Merging Params

by Jason on Feb 18, 2007

Update: This article is now about reinventing the wheel and showing you how easy it is to use the ruby language to accomplish your tasks. As robert pointed out in the comments, rails comes with the reverse merge method which you could call instead.

In this article, I'm going to show you how to have the params keep their state on your page in a very easy, concise manner. In most Rails pages you're only going to be changing the params for one object, possibly two. Conventionally, it's done like this:


<%= link_to 'Previous page', { :page => @post_pages.current.previous } %>

But that becomes a problem if you want to have anything other than the page variable in your query string. A solution to that:


<%= link_to 'Previous page', { :page => @post_pages.current.previous }.merge(params.reject{|k,v| k=="page"}) %>

Not so bad if you only have a couple of links. But I'm lazy and strive for minimalist code. Try throwing something like this in your application_helper

1
2
3
def params_merge(opts={})
  opts.merge(params.reject{|k,v| opts.keys.include?(k)})
end

And you can now do this:


<%= link_to 'Previous page', params_merge(:page => @post_pages.current.previous) %>

You might be working with a RESTful application, in which case you may have some routes that are defined like this:

1
2
3
4
5
6
map.with_options :controller => 'surf_reports', :action => 'worldwide' do |worldwide|
  worldwide.global_news     '/news',     :category => 'news'
  worldwide.global_events   '/events',   :category => 'events'  
  worldwide.global_latest   '/latest',   :category => 'latest'
  worldwide.global_location '/location', :category => 'location'
end

Now you know your route for global_news_path already has a category set. Let's say you want to link to that with seven other params. You can't just do global_news_path(params) because you might be in /events at the time, which already has the category set. Just like above, you could do something like this:


<%= link_to "News", global_news_path(params.reject{|k,v| k=="category"}) %>

But this quickly becomes tedious. Try this in your application_helper:

1
2
3
def params_without(name)
  params.reject{|k,v| k==name}
end

And now you can do an easy link_to:


<%= link_to "News", global_news_path(params_without :category) %>

Now you have some view code that's concise and pretty.


Update!
Duncan Beevers posted the following in the comments if you want something to work on all of your hashes. Thanks, Duncan!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Hash

  # lets through the keys in the argument
  # >> {:one => 1, :two => 2, :three => 3}.pass(:one)
  # => {:one=>1}
  def pass(*keys)
    tmp = self.clone
    tmp.delete_if {|k,v| ! keys.include?(k) }
    tmp
  end

  # blocks the keys in the arguments
  # >> {:one => 1, :two => 2, :three => 3}.block(:one)
  # => {:two=>2, :three=>3}
  def block(*keys)
    tmp = self.clone
    tmp.delete_if {|k,v| keys.include?(k) }
    tmp
  end

end

AddThis Social Bookmark Button Tags: rails  | permalink


Comments

Leave a response

Duncan BeeversFebruary 19, 2007 @ 02:31 PM

A bit more versatile. class Hash # lets through the keys in the argument # >> {:one => 1, :two => 2, :three => 3}.pass(:one) # => {:one=>1} def pass(*keys) tmp = self.clone tmp.delete_if {|k,v| ! keys.include?(k) } tmp end # blocks the keys in the arguments # >> {:one => 1, :two => 2, :three => 3}.block(:one) # => {:two=>2, :three=>3} def block(*keys) tmp = self.clone tmp.delete_if {|k,v| keys.include?(k) } tmp end end


Peter CooperFebruary 19, 2007 @ 04:59 PM

For the first example, wouldn’t this be better?

<%= link_to ‘Previous page’, :overwrite_params => { :page => @post_pages.current.previous } %>

I guess I might be missing something here, but I use overwrite_params all the time for this sort of thing.


robertFebruary 19, 2007 @ 04:59 PM

For the first case, that’s built into rails: http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Hash/ReverseMerge.html#M000357, and I think for your second example it’d work too, since the right category is already in the hash and you’re trying to avoid overwriting it.


facetsFebruary 20, 2007 @ 02:28 AM

what robert said, reverse_merge comes from the Ruby Facets (gem) project…

facets.rubyforge.org


edbondFebruary 20, 2007 @ 09:02 AM

Hi!

As Peter said, overwrite_params in url_for will help. If you want to remove parameter set value in hash to nil. <%= link_to ‘users’, :overwrite_params => {:tags=>nil, :page=>nil} %>


daniellibanoriMarch 16, 2007 @ 10:52 PM

def params_merge(opts={}) opts.merge(params.reject{|k,v| opts.keys.include?(k)}) end

or just

def params_merge(opts={}) params.clone.merge(opts) end


Sorry, comments are closed for this Post, but feel free to email Gregg & Jason with your input. We'd love to hear it.