30

I have a logic where I allow sorting on price and relevance. I am doing this by passing parameters to controller. My URL has a parameter - 'sort' which can have a value - 'price_lowest' or 'default'. The links looks like:

<a href="<%= request.fullpath + '&sort=price_lowest' %>">lowest prices</a> | 
<a href="<%= request.fullpath + '&sort=default' %>">relevance</a>

problem with the above code is that it "adds" parameters and does not "replace" them. I want to replace the value of &sort= parameter without adding a new value. E.g. I don't want :

../&sort=price_lowest&sort=price_lowest&sort=default

With the current logic - I am getting the above behaviour. Any suggestions ?

Ved
  • 3,380
  • 6
  • 40
  • 51

5 Answers5

67

In order to preserve the params I did this:

<%= link_to 'Date', params.merge(sort: "end_date") %>

However the url will be ugly.

UPDATE

For Rails 5 use:

<%= link_to 'Date', request.params.merge(sort: "end_date") %>
Deepak Mahakale
  • 22,834
  • 10
  • 68
  • 88
lulalala
  • 17,572
  • 15
  • 110
  • 169
  • 14
    why not `<%= link_to 'Date', params.merge(:sort => "end_date") %>` btw your name looks interesting , like Chinese... – Siwei Apr 01 '12 at 09:27
  • 7
    In rails 5 you need to use request.params <%= link_to 'Date', request.params.merge(:sort => "end_date") %> – miguel savignano Sep 21 '16 at 14:33
  • 2
    For Rails 5, https://github.com/rails/rails/issues/26289 is a good read, with an explanation of the risks and examples of solutions. – Henrik N Oct 14 '16 at 08:55
33

If you only need one cgi param and want to stay on the same page, this is very simple to achieve:

<%= link_to "lowest prices", :sort => "price_lowest" %>

However, if you have more than one, you need some logic to keep old ones. It'd probably be best extracted to a helper, but essentially you could do something like this to keep the other params..

<%= link_to "lowest prices", :sort => "price_lowest", :other_param => params[:other] %>

Named routes will only really help you here if you need to go to another page.

idlefingers
  • 31,659
  • 5
  • 82
  • 68
  • 5
    This removes the existing parameters. I end up with - http://localhost:3000/view?sort=price_lowest - all other previous parameters are lost. – Ved Jul 09 '11 at 05:10
  • Can you elaborate your solution a bit ? – Ved Jul 09 '11 at 11:32
  • As I said in the answer, if you have more than the one param, you'll need to have some logic to keep the ones you're not setting. A helper is definitely the best place for something like this. Here's a (potentially flawed, poorly written and untested) example of how to achieve that: https://raw.github.com/gist/e22343babcb76381304c/7a419e8f5333fc34a52df914d9794078fa5f1af5/gistfile1.txt – idlefingers Jul 11 '11 at 19:43
  • And if you only need the url you can use `url_for params.merge( sort: 'price_lowest' )` – Joshua Pinter Apr 02 '15 at 05:19
  • Does not work, loses all other params – Fabian de Pabian Jun 17 '15 at 13:15
  • @FabiandePabian Using `params.merge( sort: "price_lowest" )` does work and keeps the existing params. If you're not seeing that, something else is going wrong with your code. _NOTE: For Rails 5, you'll need to use `request.params.merge`._ – Joshua Pinter Jan 27 '20 at 02:14
  • @JoshuaPinter as per the next answer it works indeed. But the answer we are commenting on loses all other params but the one provided to `link_to` – Fabian de Pabian Jan 28 '20 at 14:30
  • @FabiandePabian Damn, you're right. Sorry, must have been cross-eyed looking at the answers. I thought you were commenting on my comment, not the answer. :) – Joshua Pinter Jan 28 '20 at 18:59
  • And if you need to add a `class` for instance: `link_to "lowest prices", { sort: "price_lowest" }, class: "button"` – Dorian Mar 15 '21 at 22:23
13

If a path is not passed to the link_to method, the current params are assumed. In Rails 3.2, this is the most elegant method for adding or modifying parameters in a URL:

<%= link_to 'lowest prices', params.merge(sort: 'end_date') %>
<%= link_to 'relevance', params.merge(sort: 'default') %>

params is a Ruby hash. Using merge will either add a key or replace the value of a key. If you pass nil as the value of a key, it will remove that key/value pair from the hash.

<%= link_to 'relevance', params.merge(sort: nil) %>

Cite:

scarver2
  • 7,887
  • 2
  • 53
  • 61
  • 1
    Combined with the `active_link_to` Gem and you'll also have nice active classes on your filters. - https://github.com/comfy/active_link_to – mtrolle Apr 29 '15 at 15:06
  • 1
    You should update your answer for strong parameters and using `params.permit!` in your controller. – wuliwong Jul 20 '17 at 19:06
1

My working solution on Rails 3.1 of course, it's hardcode, and has to be refactored.

item model

  def self.get(field,value)
    where(field=>value)
  end

items controller

@items=Item.all
 if params[:enabled]
  @items=@items.get(:enabled, params[:enabled])
end
if params[:section]
  @items=@items.get(:section_id, params[:section])
end

items helper

def filter_link(text, filters={}, html_options={})
  trigger=0
  params_to_keep = [:section, :enabled]
  params_to_keep.each do |param|
    if filters[param].to_s==params[param] && filters[param].to_s!="clear" || filters[param].to_s=="clear"&&params[param].nil?
      trigger=1
    end
    if filters[param]=="clear"
      filters.delete(param)
    else
      filters[param]=params[param] if filters[param].nil?
    end
  end
  html_options[:class]= 'current' if trigger==1
  link_to text, filters, html_options
end

items index.html.erb

<%= filter_link 'All sections',{:section=>"clear"} %>
<% @sections.each do |section| %>
   <%= filter_link section.title, {:section => section} %>
<% end %>

<%= filter_link "All items", {:enabled=>"clear"} %>
<%= filter_link "In stock", :enabled=>true %>
<%= filter_link "Not in stock", :enabled=>false %>
Alex Freshmann
  • 481
  • 5
  • 14
0

It's not quite the answer to the question that you're asking, but have you thought about using the Sorted gem to handle your sorting logic and view links?

Dave A-R
  • 1,159
  • 1
  • 11
  • 22