Dynamic / Regex params in ApplicationController - ruby-on-rails

How to permit dynamic params in an AppicationController?
so all these parameters should permitted:
params = { "filter_color" => "blue,green",
"filter_size" => "xl,sm,lg",
"filter_type" => "new,old,used",
"limit" => "10" }
But my approach only passes limit,
def product_params
params.permit(:limit, /filter_.*/)
end

The permit method only processes an incoming value if it's a Symbol, String, or Hash.
If you want to try to work around this you could do something like this:
filter_names = params.keys.select { |key| key[/\Afilter_.*/] }
params.permit(:limit, *filter_names)
But be aware that the point of Strong Parameters is to define an explicit set of allowed values to avoid security problems with mass-assigning user-provided values. As long as it's always safe to allow any user to pass in any kind of filter_* value, then you should be OK.

Related

How can I selectively add query parameters in redirect_to?

In my application, the session hash can contain the keys sort and ratings (in addition to _csrf_token and session_id), depending on what action a user takes. That is, it can contain both of them, either one of them, or neither, depending on what a user does.
Now, I wish to call redirect_to in my application and, at the same time, restore any session information (sort or ratings) the user may have provided.
To do this, I want to insert whatever key-value session has currently got stored (out of sort and ratings) as query parameters in my call to redirect_to. So, the path might look something like /movies?sort=...&ratings=....
I don't know how to write the logic for this. How can I do this? And how do I go about selectively inserting query parameters while calling redirect_to? Is it even possible to do this?
Any help is greatly appreciated. Thank you in advance.
First just compose a hash containing the parameters you want - for example:
opts = session.slice(:sort, :ratings)
.merge(params.slice(:sort, :ratings))
.compact_blank
This example would contain the keys :sort, :ratings with the same keys from the parameters merged on top (taking priority).
You can then pass the hash to the desired path helper:
redirect_to foos_path(**opts)
You can either just pass a trailing hash option or use the params option to explitly set the query string:
irb(main):007:0> app.root_path(**{ sort: 'backwards' })
=> "/?sort=backwards"
irb(main):008:0> app.root_path(params: { ratings: 'XX' })
=> "/?ratings=XX"
irb(main):009:0> app.root_path(params: { })
=> "/"
An empty hash will be ignored.
If your calling redirect_to with a hash instead of a string you can add query string parameters with the params: key:
redirect_to { action: :foo, params: opts }
If you're working with an arbitrary given URL/path and want to manipulate the query string parameters you can use the URI module together with the utilities provided by Rack and ActiveSupport for converting query strings to hashes and vice versa:
uri = URI.parse('/foo?bar=1&baz=2&boo=3')
parsed_query = Rack::Utils.parse_nested_query(uri.query)
uri.query = parsed_query.except("baz").merge(x: 5).to_query
puts uri.to_s # => "/foo?bar=1&boo=3&x=5"

Requiring properties in array of hash without looping in Rails

In Rails 4.2, I'd like to validate that every hash of an array passed as a parameter to my action has certain attributes.
For now I could only find how to filter out unwanted attributes, such as:
ActionController::Parameters.new(
points: [{lat: 42, foo: 0}, {lng: 43, bar: 100}]
).permit(
points: [:lat, :lng]
)
# => {"points"=>[{"lat"=>42}, {"lng"=>43}]}
What I'd like to do is making sure every member of points has both lat and lng without having to loop over it. Is this possible using permit or a similar method?
There is a method called require that has the same signature as permit:
params.require(:lat, :lng)
Note that you can chain this with permit
Also, you can use select or reject on the params hash, secure params is mostly sugar for this anyway.
def my_params
required_attrs = %w{lat lng}
missing_params = required_attrs.select do |key|
params.has_key?(key)
end
missing_params.empty? ? params : raise(RuntimeError, "missing params: #{missing_params.join(",")}")
end

How to permit all values from a hash

I'm working in Ruby on Rails and I'm trying to permit all values from a hash using Ruby's permit function. It seems rather simple, but I just cannot get this to work. I've already reviewed the references on permit, and answers to this SO question how to permit an array with strong parameters.
Here's my code
PERMITTED_PARAMS = [
:OriginCity,
:OriginState,
{ :PickupDates => {}}
].freeze
params = {"OriginCity"=>"Denver", "OriginState"=>"CO", "PickupDates"=>{"0"=>"2016-09-30"}}
filtered_params = params.permit(PERMITTED_PARAMS)
And, the resulting value for filtered_params is
{"OriginCity"=>"Denver", "PickupDates"=>{}}
While the desired value for filtered_params is
{"OriginCity"=>"Denver", "PickupDates"=>{"0":"2016-09-30"}}
Any advice on how to obtain the desired value by changing PERMITTED_PARAMS?
You want to permit all values in a hash, not an array, which is different from the example you linked to.
Try this:
PERMITTED_PARAMS = [
:OriginCity,
:OriginState
].freeze
params = {"OriginCity"=>"Denver", "OriginState"=>"CO", "PickupDates"=>{"0"=>"2016-09-30"}}
filtered_params = params.permit(PERMITTED_PARAMS).tap do |whitelisted|
whitelisted[:PickupDates] = params[:PickupDates]
end
See also: Strong parameters: allow hashes with unknown keys to be permitted

When to use slice vs. permit in ActionController::Parameters?

Assuming I have an ActionController::Parameters object like
params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
I can call slice on it or permit to get/allow only certain parameters.
At first sight, they return the same thing
> params.slice(:a)
=> {"a"=>1}
> params.permit(:a)
[18:21:45.302147] Unpermitted parameters: b, c
=> {"a"=>1}
But if I call to_h on it params.slice(:a).to_h returns an empty hash, while params.permit(:a).to_h returns an hash with key :a. As far as I understood, this is the case, because :a was not permitted.
What I wonder now is, what is the use case of slice, if I could just use permit?
One difference I could think of is permit cuts nested hash if you don't explicitly specify the nested keys while slice allows nested hash:
# params = { a: 'a', nested: { nested_1: 1, nested_2: 2 } }
params.permit(:a, :nested) # => { a: 'a' }
params.slice(:a, :nested) # => { a: 'a', { nested_1: 1, nested_2: 2 } }
Another difference is in Rails 4, permit won't raise ActiveModel::ForbiddenAttributes when calling in .update_attributes(...) (answered here):
user.update_attributes(params.slice(:email)) # will raise ActiveModel::ForbiddenAttributes
user.update_attributes(params.permit(:email)) # wont raise error
slice gives ability to slice a hash with selected keys.
where as .permit returns a new ActionController::Parameters instance that includes only the given filters and sets the permitted attribute for the object to true. This is useful for limiting which attributes should be allowed for mass updating.
I would say slice is for everything dealing with Hash and permit is created using slice pattern but more in context of url params.
Hope it helps!
Also read this: http://apidock.com/rails/ActionController/Parameters/permit

Editing params nested hash

Assume we have a rails params hash full of nested hashes and arrays. Is there a way to alter every string value (whether in nested hashes or arrays) which matches a certain criteria (e.g. regex) and still keep the output as a params hash (still containing nested hashes arrays?
I want to do some sort of string manipulation on some attributes before even assigning them to a model. Is there any better way to achieve this?
[UPDATE]
Let's say we want to select the strings that have an h in the beginning and replace it with a 'b'. so we have:
before:
{ a: "h343", b: { c: ["h2", "s21"] } }
after:
{ a: "b343", b: { c: ["b2", "s21"] } }
For some reasons I can't do this with model callbacks and stuff, so it should have be done before assigning to the respective attributes.
still keep the output as a params hash (still containing nested hashes arrays
Sure.
You'll have to manipulate the params hash, which is done in the controller.
Whilst I don't have lots of experience with this I just spent a bunch of time testing -- you can use a blend of the ActionController::Parameters class and then using gsub! -- like this:
#app/controllers/your_controller.rb
class YourController < ApplicationController
before_action :set_params, only: :create
def create
# Params are passed from the browser request
#model = Model.new params_hash
end
private
def params_hash
params.require(:x).permit(:y).each do |k,v|
v.gsub!(/[regex]/, 'string')
end
end
end
I tested this on one of our test apps, and it worked perfectly:
--
There are several important points.
Firstly, when you call a strong_params hash, params.permit creates a new hash out of the passed params. This means you can't just modify the passed params with params[:description] = etc. You have to do it to the permitted params.
Secondly, I could only get the .each block working with a bang-operator (gsub!), as this changes the value directly. I'd have to spend more time to work out how to do more elaborate changes.
--
Update
If you wanted to include nested hashes, you'd have to call another loop:
def params_hash
params.require(:x).permit(:y).each do |k,v|
if /_attributes/ ~= k
k.each do |deep_k, deep_v|
deep_v.gsub!(/[regex]/, 'string'
end
else
v.gsub!(/[regex]/, 'string')
end
end
end
In general you should not alter the original params hash. When you use strong parameters to whitelist the params you are actually creating a copy of the params - which can be modified if you really need to.
def whitelist_params
params.require(:foo).permit(:bar, :baz)
end
But if mapping the input to a model is too complex or you don't want to do it on the model layer you should consider using a service object.
Assuming you have a hash like this:
hash = { "hello" => { "hello" => "hello", "world" => { "hello" => "world", "world" => { "hello" => "world" } } }, "world" => "hello" }
Then add a function that transforms the "ello" part of all keys and values into "i" (meaning that "hello" and "yellow" will become "hi" and "yiw")
def transform_hash(hash, &block)
hash.inject({}){ |result, (key,value)|
value = value.is_a?(Hash) ? transform_hash(value, &block) : value.gsub(/ello/, 'i')
block.call(result, key.gsub(/ello/, 'i'), value)
result
}
end
Use the function like:
new_hash = transform_hash(hash) {|hash, key, value| hash[key] = value }
This will transform your hash and it's values regardless of the nesting level. However, the values should be strings (or another Hash) otherwise you'll get an error. to solve this problem just change the value.is_a?(Hash) conditional a bit.
NOTE that I strongly recommend you NOT to change the keys of the hash!

Resources