How to use query parameter like strong parameter? - ruby-on-rails

How can i use query parameter as a strong parameter.
This is my POST /tag method called by frontend to search posts.
def tag
if params[:category] == 'Shop'
render json: ShopPostPopulator.new(params[:search]).run
else
render json: Part.search(params[:search])
end
end
If i want to use strong parameter instead of 'params[:search]', how should I do it.

ActionController::Parameters is really just a hash like object and "strong parameters" is really just the equivalent of using Hash#slice to only allow a whitelist of attributes through. Which protects against mass assignment attacks. Beginners and often experienced Rails devs. seem to think that it magically filters and cleans the parameters. It doesn't - it just prevents you from getting a mass injection attack out of ignorance, stupidy or laziness.
Whitelisting is only needed if you are assigning a hash of parameters to a model:
User.update(
params.permit(:email, :password)
)
In this case it prevents a malicious user from for example passing role=superadmin or id=1 (as the first user is often the admin). If you are just assigning a single attribute from the params hash you don't need to use strong attributes. The major difference introduced back in 2012 is that whitelisting became manditory as an error is raised if you pass a ActionController::Parameters object without the #permitted = true attribute to .new, .update, .create and the other methods that spawn or update records.
If you want to though you can use ActionController::Parameters#permit to ensure that the parameter is a simple scalar type (not a hash or array):
params.permit(:search).fetch(:search, nil)
If search is an optional parameter with nested keys you can whitelist it like so:
params.fetch(:search, {}).permit(:foo, :bar)
You can also make the parameter required so that a ActionController::ParameterMissing exception is raised if its missing:
params.require(:search).permit(:foo, :bar)
Which is what you do 99% of the time in Rails since it bails early if we can't do anything meaning with the request.

Related

Updating objects in RoR with API call

I am trying to configure my ruby on rails application in such a manner that I can update values with http Patch calls from for example a Angular app. Currently I have the following method of which I expect it to work:
users_controller.rb
def safe_params
params.require(:id).permit(:email)
end
def update
user = User.find(params[:id])
user.update_attributes(safe_params)
render nothing: true, status: 204
end
However, I get the following error when I pass some simple JSON:
undefined method `permit' for "500":String
Passed JSON:
{"email":"newadres#live.com", "id":500}
Do you guys know what I am doing wrong?
I believe you are misunderstanding the purpose of require and permit.
require is generally used in combination with a Hash and a form, to make sure the controller receives an Hash that exists and contains some expected attributes. Note that require will either raise, or extract the value associated with the required key, and return that value.
permit works as a filter, it explicitly allows only certain fields. The returned value is the original params Hash, whitelisted.
In your case, require does not make any sense at all, unless you pass a nested JSON like this one
{"user": {"email":"newadres#live.com", "id":500}}
but even in that case, it would be
params.require(:user).permit(:email)
In your current scenario, the correct code is
params.permit(:email)
One way to fix this, keeping with the spirit of the Rails docs:
def safe_params
params.require(:user).permit(:email)
end
And update the json:
{"user": {"email":"newadres#live.com"}, "id": 500}
You should change the order between require and permit, like that
params.permit(:email).require(:id)
because permit returns the entire hash, while require returns the specific parameter
Reference here
UPDATE
However, as others pointed out, you shouldn't use require with a single attribute, as it is most commonly used for hashes instead

In Rails, how can a mass-assignment be made to an existing model object without warnings?

In a Rails application it's possible to assign some values from params to an existing model object, like this:
model.attributes = params
This would obviously return a ForbiddenAttributesError but that can be avoided like this:
model.attributes = params.permit(:a, :b, :c)
However, although that works, it still outputs a message to the console if params contains keys that are not mentioned in the call to permit:
Unpermitted parameters: d, e, f
The warning is pointless because it's already known that params contains additional keys and permit is being used to select the required subset. It can be avoided by doing
model.attributes = params.slice(:a, :b, :c).permit!
Is there a more appropriate way to do this assignment that does not require having to slice the hash first?
The warning is pointless because it's already known that params contains additional keys and permit is being used to select the required subset.
You mistake the purpose of this warning. It is very valuable when you debug why your form doesn't update this new field you added just today. With the warning, looking at the server log you can quickly find out that you forgot to update strong_params filtering.
Also, the warning will only be shown in dev/test environments. It won't appear in production.
But suppose you really hate that warning. In which case you can do this in your app config:
# possible values: :raise, :log
config.action_controller.action_on_unpermitted_parameters = false
BTW, your code is a bit unorthodox. The common way is to keep the filtering code in one method and call it from both create/update (and whereever else you need it)
def update
#product = ...
if #product.update_attributes(product_params)
...
else
...
end
end
private
def product_params
params.require(:product).permit(:a, :b, :c)
end

What's the use case for params.require?

I don't really understand the purpose of params.require - more specifically, what the use is for it to raise an exception when a param is missing.
I know I might want some params to be present for a given request, but why would I want to return a 500 server error if any of them are missing? Why wouldn't I traverse params for the parameters I want using conditional logic and return flash[:error] instead?
This is due to Rails using Strong Parameters, see Strong Parameters. Ive pasted the section on Strong Parameters at the bottom of the answer
Unlike other platforms that whitelist everything and require blacklisting of parameters, Rails does the opposite. Everything is blacklisted and you are required to whitelist the parameters you wish to allow through mass assignment.
Hence why if you try to save an object and haven't permitted a certain param, you will see a "Unpermitted parameters:" in your log/console.
Its a best practice to perform save/update actions through requiring and whitelisting params you wish to permit.
Strong Parameters
With strong parameters, Action Controller parameters are forbidden to be used in Active Model mass assignments until they have been whitelisted. This means you'll have to make a conscious choice about which attributes to allow for mass updating and thus prevent accidentally exposing that which shouldn't be exposed.
In addition, parameters can be marked as required and flow through a predefined raise/rescue flow to end up as a 400 Bad Request with no effort.
class PeopleController < ActionController::Base
# This will raise an ActiveModel::ForbiddenAttributes exception
# because it's using mass assignment without an explicit permit
# step.
def create
Person.create(params[:person])
end
# This will pass with flying colors as long as there's a person key
# in the parameters, otherwise it'll raise a
# ActionController::ParameterMissing exception, which will get
# caught by ActionController::Base and turned into that 400 Bad
# Request reply.
def update
person = current_account.people.find(params[:id])
person.update!(person_params)
redirect_to person
end
private
# Using a private method to encapsulate the permissible parameters
# is just a good pattern since you'll be able to reuse the same
# permit list between create and update. Also, you can specialize
# this method with per-user checking of permissible attributes.
def person_params
params.require(:person).permit(:name, :age)
end
end

How does params[:id] not throw an exception even if we do not explicitly whitelist :id?

In rails 4.x, strong_parameters require parameters to be explicitly permitted. Yet, in the following example, I do NOT get a ForbiddenAttributesError - why does :id not throw when in the show action even though it is not explicitly permitted?
def FooController
...
def show
#foo = Foo.find(params[:id]) # why no exception here?
end
private
def foo_params
params.require(:foo).permit(:name, :address) # note: No :id here
end
end
See: http://edgeguides.rubyonrails.org/action_controller_overview.html#strong-parameters
"With strong parameters, Action Controller parameters are forbidden to be used in Active Model mass assignments until they have been whitelisted."
Doing a find is completely valid, and is, in fact, shown in the example in the documentation linked to, above.
Strong parameters are used only for assignment of attributes. You can freely search and perform other operations with any param, just not mass assignment.
You can see more in-depth explanation and examples in Rails Guides
For Rails, params[:id] outside from default params.
Query string:
www.example.com/foo/123?bar=1&baz=2
Request path:
www.example.com/foo/123 where 123 is params[:id]
Paramerts:
bar=1&baz=2 this can be permitted
If you pass 123 to parameters then you need permitted :id.
There is no need of explicitly permitting the :id unless you want to.Rails will do it implicitly.If want to check whether the :id is whitelisted or not,you can do puts params[:foo] after it is created or you can just see the log.you will see something like this
{id=>some_id, "name"=>"some_name", "adddress"=>"some_address"}
So,defining a Foo object like this
#foo = Foo.find(params[:id])
will not throw an exception.
Hope it helped!

Rails: Iterate dynamically over params hash to verify they exist

I've got a form with quite a bit of params being passed to the controller for processing. The different 'sets' of params are named in a similar fashion:
setname1_paramname
setname1_paramname2
Now, I need to check one of these 'sets' to verify that all of the fields are submitted. Right now, I'm doing this with a manual If Or style statement:
if setname1_paramname.blank? || setname1_paramname2.blank? || ...etc
#object.errors.add_to_base("All setname1 fields are required.").
render :action => 'new'
return false
end
Is there way to programmatically loop over these params, and add them to the #object errors?
Thanks!
Since it sounds like you have a ton of params and also seems like you need to be able to do checks on groups of params, maybe something like this would be useful? Basically, iterate over the params hash, and use regular expressions to target sets of params. Then, inside the loop, you can do any sort of validations:
params.each do |key, value|
# target groups using regular expressions
if (key.to_s[/setname1.*/])
# whatever logic you need for params that start with 'setname1'
if param[key].blank?
#object.errors.add_to_base("All setname1 fields are required.").
end
end
end
If the names are arbitrary and of your own choosing, you could make virtual attributes for them in your model and let Rails handle the presence checking.
class SomeModel < ActiveRecord::Base
VIRTUAL_ATTRIBUTES = [:billing_address, :billing_state, :something_else]
attr_accessor *VIRTUAL_ATTRIBUTES
validates_presence_of *VIRTUAL_ATTRIBUTES
…
end
Is there a reason you wouldn't just store this information in a model, even if temporarily, and then just use rails validations for your information?
I'm rusty but I assume that even if the value is blank the param will still be returned in the params hash as long as it is coming from a form element, yes? Could you just iterate through the params hash and keep a counter of how many values are not blank and then compare the length of the params hash to the counter. If the counter is short then you have blank parameters and can handle the error that way without having to hardcode checks for each individual parameter, yes?
If what you need is a multi-step form as I suspect, you may find the Railscast on Multistep Forms to be useful

Resources