module MergeParams::Helpers
Public Instance Methods
Adds params to the query string (Unlike url_for_merge, which tries to generate a route from the params.)
# File lib/merge_params/helpers.rb, line 112 def add_params(url = request.fullpath, new_params = {}) uri = URI(url) # Allow keys that are currently in query_params to be deleted by setting their value to nil in # new_params (including in nested hashes). merged_params = parse_nested_query(uri.query || ''). deep_merge(new_params).recurse(&:compact) uri.query = Rack::Utils.build_nested_query(merged_params).presence uri.to_s end
Safely merges the given params with the params from the current request
# File lib/merge_params/helpers.rb, line 59 def merge_params(new_params = {}) params_for_url_for. deep_merge(new_params.deep_symbolize_keys) end
Safely merges the given params with the params from the current request, then generates a route from the merged params. You can remove a key by passing nil as the value, for example {key: nil}.
# File lib/merge_params/helpers.rb, line 90 def merge_url_for(new_params = {}) url = url_for(merge_params(new_params)) # # Now pass along in the *query string* any params that we couldn't pass to url_for because they # # were reserved options. # query_params_already_added = parse_nested_query(URI(url).query || '') # # Some params from new_params (like company_id) that we pass in may be recognized by a route and # # therefore no longer be query params. We use recognize_path to find those params that ended up # # as route params instead of query_params but are nonetheless already added to the url. # params_already_added = Rails.application.routes.recognize_path(url).merge(query_params_already_added) params_already_added = params_from_url(url) query_params_to_add = params_for_url_for(new_params). recursively_comparing(params_already_added).graph { |k,v, other| if v.is_a?(Hash) || v.nil? || other.nil? [k, v] end } add_params(url, query_params_to_add) end
Params that can safely be passed to url_for to build a route. (Used by merge_url_for.)
We exclude RESERVED_OPTIONS such as :host because such options should only come from your app code. Allowing :host to be set via query params, for example, means a bad actor could cause links that go to a different site entirely:
# Request for /things?host=somehackingsite.ru url_for(params) => “somehackingsite.ru/things”
Similarly, the :controller and :action keys of `params` never come from the query string, but from `path_parameters`. (TODO: So why not just use params.except(…)?)
TODO: Why not allow :format from params? To force people to use .:format? But doesn't that also come through as params?
(And we don't even need to pass the path_parameters on to url_for because url_for already includes those (from :_recall)
# File lib/merge_params/helpers.rb, line 48 def params_for_url_for(params = params()) params = params.to_unsafe_h if params.respond_to?(:to_unsafe_h) params.deep_symbolize_keys.except( *ActionDispatch::Routing::RouteSet::RESERVED_OPTIONS, :controller, :action, :format ) end
Parsing helpers
# File lib/merge_params/helpers.rb, line 133 def params_from_url(url) query_params = parse_nested_query(URI(url).query || '') route_params = Rails.application.routes.recognize_path(url.to_s) params_for_url_for( route_params.merge(query_params) ) end
# File lib/merge_params/helpers.rb, line 141 def parse_nested_query(query) Rack::Utils.parse_nested_query(query || '').deep_symbolize_keys end
Returns a hash of params from the query string (en.wikipedia.org/wiki/Query_string), with symbolized keys.
# File lib/merge_params/helpers.rb, line 26 def query_params request.query_parameters.deep_symbolize_keys end
request.parameters (which also includes POST params) but with only those keys that would normally be passed in a query string (without :controller, :action, :format) and with symbolized keys.
# File lib/merge_params/helpers.rb, line 19 def query_params_from_request_params request.parameters.deep_symbolize_keys. except(*request.path_parameters.deep_symbolize_keys.keys) end
request.parameters but with symbolized keys.
# File lib/merge_params/helpers.rb, line 12 def request_params request.parameters.deep_symbolize_keys end
Easily extract just certain param keys.
Can't use permit().to_h — for example,
params.permit(:page, :per_page, :filters).to_h
or you'll get an error about whatever other unrelated keys happen to be set:
found unpermitted parameters: :utf8, :commit, :company_id
One good solution might be to have a permitted_params method defined with all of your permitted params for this controller, and then you could make other methods that fetch subsets of those params using slice. But if you don't want to do that, this slice_params
helper is another good option.
Other options include:
-
You could add those unrelated keys to always_permitted_parameters … but that only works if all of them should be permitted everywhere — there are probably controller-specific params present that are permitted for this controller.
-
You could also change action_on_unpermitted_parameters — but unfortunately, there's no way to pass a temporary override value for that directly to permit, so the only option is to change it temporarily globally, which is inconvenient and not thread-safe.
# File lib/merge_params/helpers.rb, line 83 def slice_params(*keys) params_for_url_for.slice(*keys) end