65

I am trying to implement a simple search and sort for my webapp. I am following the railscast and this railscast.

My application helper for sortable function which I am using as link is:

def sortable(column, title = nil)
      title ||= column.titleize
      css_class = column == sort_column ? "current #{sort_direction}" : nil
      direction = column == sort_column && sort_direction == "asc" ? "desc" : "asc"
      link_to title, params.merge(:sort => column, :direction => direction, :page => nil), {:class => css_class}
    end

I am using these in the view. In the controller I am using white listing as:

 @listingssearch.where(:vehicletype => 'Car').order(sort_column + " " + sort_direction).paginate(:page => params[:page], :per_page => 30)

Private Methods for sanitization:

 private
     def sort_column
          Listing.column_names.include?(params) ? params[:sort] : "rateperhour"
        end

        def sort_direction
          %w[asc desc].include?(params[:direction]) ? params[:direction] : "asc"
        end

I tried using merge in the private method:

(Listing.column_names + params) but its not working 

For the helper methods I am getting an error when I am trying to provide search params to the sorting link: unable to convert unpermitted parameters to hash

It shows the error is for merge

link_to title, params.merge(:sort => column, :direction => direction, :page => nil), {:class => css_class}

The otherway around works fine:

<%= bootstrap_form_for listings_path, :method => 'get' do %>

        <%= hidden_field_tag :direction, :value => params[:direction] %>
        <%= hidden_field_tag :sort,:value => params[:sort] %>



        <div class= "col-sm-12 col-lg-12 col-md-12" style = "margin: auto;">
            <h6 style = "color:#7C064D;"><strong> PICK A DATE  <span class="glyphicon glyphicon-calendar"></span></strong>
            <%= date_field_tag :startdate, params[:startdate], placeholder: 'DATE' %>           
            </h6>
        </div>  

        <div class= "col-sm-12 col-lg-12 col-md-12" style = "margin: auto;">    
        <p>     
            <%= text_field_tag :near, params[:near], placeholder: ' Destination' %>
            <%= text_field_tag :radius, params[:radius], placeholder: ' Search Radius' %>
        </p>
        </div>      
        <div class= "col-sm-12 col-lg-12 col-md-12" style = "margin: auto;">    
        <p>     
            <%= text_field_tag :min, params[:min], placeholder: ' Minimum Rate Per Hour' %>
            <%= text_field_tag :max, params[:max], placeholder: ' Maximum Rate Per Hour' %>
        </p>
        </div>

        <div class= "col-sm-12 col-lg-12 col-md-12" style = "margin-top: 10px;">        
            <%= submit_tag "Search", class: "btn btn-info", style: "width: 40%; background-color: #E20049; border: #e20049;" %>
            <%= link_to 'View All', root_path, class: "btn btn-info", style: "width: 40%; background-color: #E20049; border: #e20049;" %>
        </div>

        <!-- <div class= "col-sm-6 col-lg-6 col-md-6" style = "margin-top: 10px;">      

        </div> -->


    <% end %>

My question is How to persist search params in sort helper methods in rails 5? What I am doing wrong?

Saurav Prakash
  • 1,177
  • 1
  • 11
  • 25

6 Answers6

108

In Rails 5, ActionController::Parameters no longer inherits from Hash, in an attempt to discourage people from using Hash-related methods on the request parameters without explicitly filtering them.

As part of this pull request, which was backported into Rails 5.1 and partially into Rails 5.0, an exception is raised if you try to call to_h on the parameters object without calling permit.

Calling merge on the original params object (params.merge(:sort => column, :direction => direction, :page => nil)) returns a new ActionController::Parameters object with the same permitted status (that is, permit has not been called on it). The link_to method then ends up calling to_h on that object, which raises the exception.

If you know which parameters should be allowed in the link, you can call permit with those listed.

params.permit(:param_1, :param_2).merge(:sort => column, :direction => direction, :page => nil)
# OR
params.merge(:sort => column, :direction => direction, :page => nil).permit(:param_1, :param_2, :sort, :direction, :page)

If you don't know which parameters could be included in the link, then it's possible to call request.parameters.merge(...) (as mentioned in this answer) or params.to_unsafe_h.merge(...). However, as pointed out in comments, this is a security risk when the result is passed to link_to, as a parameter like host would be interpreted as the actual host for the link instead of a query parameter. There are several other keys that also have special meaning in link_to (everything accepted by url_for, plus :method), so it's generally a risky approach.

Max
  • 1,817
  • 1
  • 10
  • 13
  • Do you mean passing search and sort params in the resource permit method itself? – Saurav Prakash Sep 04 '17 at 08:43
  • @SauravPrakash I expanded the answer a bit to include some examples and also to make a slight correction to what's causing the exception. Does that address your question? – Max Sep 04 '17 at 17:33
  • Thanks! Passing params explicitly will help protect against sql injections. This would be the right way to proceed instead of request.params. Although both works. – Saurav Prakash Sep 04 '17 at 19:36
  • This actually is risky, because someone can include say `host` in the query params of the URL they ask your app for, and your app will generate a url to some other host. – jrochkind Sep 17 '19 at 15:22
  • @jrochkind Good point for the options that circumvent strong parameters; I'll edit to note that. I don't believe the options that explicitly name the allowed parameters would be vulnerable to that. – Max Sep 18 '19 at 01:57
  • Note that this can get tricky when you're trying to pass parameters as well as ```.merge({format: :csv})``` - e.g. if you're trying to export a ransack search. The correct format for your link_to needs to be: ```impairments_path({q: params.require(:q).permit(:location_id_eq, :system_name_cont, :classification_eq, :ended_eq)}.merge({format: :csv}))```. I posted a more detailed answer [here](https://stackoverflow.com/a/76734567/16952511) – Nick Gobin Jul 21 '23 at 01:30
49

You can use this hack:

params.to_enum.to_h

I think rails developers will restrict it when they know it's the way to use unpermitted parameters. :)

Ghazi
  • 966
  • 8
  • 15
12

you can try to use request.parameters.merge, below is sample for your code above

<%= link_to title, listings_path(request.parameters.merge({:sort => "column", :direction => "direction", :page => nil})), :class => "form-control css_class" %>  
widjajayd
  • 6,090
  • 4
  • 30
  • 41
  • 1
    Thank You! I used your suggestion and found out one needs to add request.params.merge instead of params.merge So the final return will be: link_to title, request.params.merge({:sort => column, :direction => direction, :page => nil}), {:class => "css_class" } which worked in rails 5 – Saurav Prakash Sep 04 '17 at 08:45
  • Wouldn't this open you up to xss attacks? – BenKoshy Jun 20 '23 at 05:37
7

You can also overwrite by doing params.permit! This may have security risks though, so be careful using this.

FutoRicky
  • 903
  • 2
  • 9
  • 22
5

I believe if you pass column to permit it should get you working again!

def sortable(column, title = nil)
    title ||= column.titleize
    css_class = column == sort_column ? "current #{sort_direction}" : nil
    direction = column == sort_column && sort_direction == "asc" ? "desc" : "asc"
    link_to title, params.permit(column).merge(sort: column, direction: direction, page: nil), { class: css_class }
end
Mike Langston
  • 51
  • 1
  • 2
  • Perfect response and context. as you always know with this function which is the allowed field (column) – Mauro Dec 03 '18 at 11:39
5

Since no one suggested this. So here is one way that we can use params.to_unsafe_hash which will convert the strong parameters to a hash.

For example:

(byebug) values
<ActionController::Parameters {"country"=>"RU", "city"=>"Moscow", "street"=>"1 First Street", "zip"=>"123456"} permitted: false>
(byebug) values.to_unsafe_hash
{"country"=>"RU", "city"=>"Moscow", "street"=>"1 First Street", "zip"=>"123456"}
Dharman
  • 30,962
  • 25
  • 85
  • 135
Ahmad hamza
  • 1,816
  • 1
  • 23
  • 46