Rails check if params exist in the controller - ruby-on-rails

I have a factor controller which takes factor values for two different types of factors
Primary Factor
Secondary Factor
And I pass them as params as follows:
class FactorController < AdminController
def create
if primary_factor_params ## LINE 5
do something
elsif secondary_factor_params
do something else
end
end
def primary_factor_params
params.require(:primary).permit(:user_id, ## LINE 70
:primary_factors)
end
def secondary_factor_params
params.require(:secondary).permit(:user_id,
:secondary_factors)
end
end
But in the above whenever I try to pass a secondary_factor I get the following error:
ActionController::ParameterMissing (param is missing or the value is empty: primary):
app/controllers/factors_controller.rb:70:in `primary_factor_params' app/controllers/api/v1/admin/factors_controller.rb:5:in `create'
So to me it seems that this error is coming up because I didn't have any values for primary_factor_params in this condition and that's way it throws the error because of the first if condition.
I've tried:
primary_factor_params.exists?
primary_factor_params.has_key?(:primary_factors)
....
But all of them throw the same error since primary_factor_params doesn't exist. Is there a way to test this without throwing an error for missing params?

The problem is this line params.require(:primary) saying that the parameter is required, so attempting to do like params.require(:primary).exists? wont help you if you do not have a :primary param at all because the require already failed.
You need to check its existance on params itself. For example params.has_key?(:primary).
Dependening on your use case, you might also use params.permit(:primary) directly on params as well. See the API docs for detailed information on how ActionController::Parameters (the class for params) can be used.
http://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-permit

you can check your params existence in your case by params[:primary].present?
I think this is the easiest way

Related

Set Parameter if blank

I need to set the id parameter to a value if it is wasn't submitted with the form.
Is it ok to do something like this in Rails or does this violate any standards or cause possible issues?
if params[:cart][:cart_addresses_attributes]["0"][:id].blank?
params[:cart][:cart_addresses_attributes]["0"][:id] = 1234 #default id
end
My implementation works with this logic, but I am not sure if this is the proper way to handle the issue.
There's a chance [:record_type] is nil which will lead to an undefined method error when you attempt to call [:id] on nil. Additionally, I'd find it a bit weird to directly mutate params, even though you technically can do that. I'd consider using Strong Parameter processing methods like so (added a full action, which isn't in your sample, to give more context on how this would be used):
def create
#record_type = RecordType.new(record_type_params)
if record_type.save
redirect_to #record_type
else
render :new
end
end
def record_type_params
params.require(:record_type).permit(:id).reverse_merge(id: 1234)
end
The reverse_merge call is a way to merge the user-supplied parameters into your defaults. This accomplishes what you're after in what I would consider a more conventional way and doesn't mutate params.
def cart_params
params.require(:cart).permit(:cart_addresses_attributes => [:id]).tap do |p|
p[:cart_addresses_attributes]["0"][:id] ||= 1234
end
end
if params[:record_type][:id].nil? # or replace ".nil?" with "== nil"
params[:record_type][:id] = 1234
end
personally, this is the way I prefer to do it. Some ways are more efficient than others, but if that works for you I'd roll with it.

How to properly use params.require in Rails

I've read several | articles about using params.require(...) in Rails, but nothing that shows them in a non-trivial, real-world scenario.
Specifically, the following URL will be called:
GET http://myapp.example.com/widgets/{clientUuid}
Where {clientUuid} will be a string. I just want to check (from the proper controller action) whether the provided {clientUuid} is non-null and non-empty. I'm wondering if I can just do this:
if params.require(params[:clientUuid]) == null
response = { "error" => "bad client uuid" }
render json: response, status: :bad_request
return
end
And have non-nullness/non-emptiness enforced? If not, what can I do to achieve my desired result?
You're overcomplicating a simple GET request by messing up the route and using a method thats meant for a completely different use.
The idea is that .requires should be used for non-idempotent request methods (POST, PUT,PATCH) where the request contains a body with parameters. It lets you take a single key from the params and whitelist the params contained - which matches the Rails ideom of nesting inputs in a hash with the name of the resource as the root key.
In that case using .requires lets you return a response code to the client that indicates that the request cannot be processed (422 - Unprocessable Entity) as the request body does not have the right structure.
While you could potentially use it creatively on a GET request its wrong from a restful application engineering standpoint. In your case you should be returning a 404 - Not found response code if the clientUuid does not match a record. Usually in rails this is done by using .find which will raise a ActiveRecord::RecordNotFound exception which the framework catches.
Additionally if you have declared the route properly in the first place rails would actually give a 404 automatically as the request would not match if the id segment is missing.
class WidgetsController < ApplicationController
def show
#widget = Widget.find(params[:clientUuid])
end
end
If you want you could bail early so that the database is never queried if the param does not match a condition:
class WidgetsController < ApplicationController
def show
raise ActiveRecord::RecordNotFound if params[:clientUuid].blank?
#widget = Widget.find(params[:clientUuid])
end
end
You can just write:
if params[:clientUuid].blank?
response = { "error" => "bad client uuid" }
render json: response, status: :bad_request
return
end
With params.require it is a bit more difficult, because require raises a ActionController::ParameterMissing exception if the parameter is missing, but allows the parameter to return false (what I guess is still invalid in your example):
begin
uuid = params.require(:cliendUuid)
rescue ActionController::ParameterMissing
# nothing to do, just ensure the exceptions is rescued
end
unless uuid
# handle missing uuid
end
Or:
begin
uuid = params.require(:cliendUuid) || raise ActionController::ParameterMissing
rescue ActionController::ParameterMissing
# handle missing uuid
end
The article you posted re strong parameters is specifically about protecting your database data from user input, usually provided by forms.
params.require(:user).permit(:username)
The above code specifies that for the model User only allow the attribute username to be touched. If you try to update or create a user record in the user table with any other attribute e.g. email, you would get an error because the email attribute has not been 'permitted'. This is what is meant by whitelisting. You will only see the above code in create or update controller methods, or any other method that amends the data in some way. (An exception, of course, is deleting a record).
In your example, the parameter is provided as part of the url which you can also access via the rails provided params hash. However, as your method is not interacting with the db, you don't need to run it through the permit method.
This resource may help.

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

Raise error on unpermitted parameters rails

I am currently trying to implement raising an error when unpermitted parameters are posted in my RoR back-end. I included
config.action_controller.action_on_unpermitted_parameters = :raise
in my development.rb configuration. Now, for example I have in one of my controllers:
def apiary_params
params.require(:apiary).permit(:name, :municipality, :prefecture, :latitude, :longitude, :numberofbeehives, :notes)
end
If I now try posting another parameter lets say "apiary[asdf]" then an internal server error is raised correctly. However if I try posting a random "asdf" param not in the apiary hash, then the request is handled without an error. Does that mean that the random "asdf" and whatever other parameter not in the apiary is permitted? How can I fix that?
No, those extra parameters are not permitted - they're silently discarded.
You're only calling permit on params.require(:apiary), that is, params[:apiary], so only extra attributes inside that hash will raise an exception.
As soon as you make that params.require call, then all other params submitted become irrelevant for the return value of this method. You're only dealing with data inside that params[:apiary] hash, and that is what will be returned.

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

Resources