Iain
Iain

Reputation: 127

Allowing only certain values though a strong parameter in Rails 4

I have a field otp_set_up, which in the company_user model is allowed to be "true" or "false".

There is a use case where a sys admin user can reset this field to "false".

While the field can be set to "true" through code, NO user can set it to "true" via a form edit etc.

I haven't added to it the validation in the model since it can be "true" or "false".

I have the following code in a params method specific to an update in the controller before the params.require .permit bit:

if curr_company_user.is_sys_admin? && curr_company_user.can_crud_company_users? && params[:id].to_i != curr_company_user.id

  params[:company_user] = params[:company_user].except(:otp_set_up) if params[:company_user][:otp_set_up] == true
  params.require(:company_user).permit(:otp_setup, etc. etc....

elsif etc. etc...

This works. A Sys admin user can not set otp_set_up to "true".

My question is:

Is this the best and correct way to do this in Rails? It seems a bit hacky to me, going through the params hash and removing a bit.

Is there a better / cleaner way?

Upvotes: 7

Views: 8490

Answers (3)

UsamaMan
UsamaMan

Reputation: 705

I have a suggestion that you set it in the params only if the user is an admin and not otherwise. I think this is a better way.

In the model, do something like this:

if user.role == 'admin'
  attr_accessor #All the params
else
  attr_accessor #All the other params except the one you want to 
  exclude

Upvotes: 0

m. simon borg
m. simon borg

Reputation: 2575

delete_if cleans it up. Still a bit hacky, but slightly less so : )

params.require(:company_user).permit(:otp_setup).delete_if do |key, val|
  key == 'otp_setup' && val == true
end

This leaves the original params object intact.

There isn't a built in way to do this. It looks like there used to be but no more https://github.com/rails/strong_parameters/issues/167

delete_if is defined on Hash in the core library, so it is probably the best way to do it in Ruby and by extension in Rails in the absence of a built in method.

Update

I thought it was an interesting idea, so I wrote a small gem called allowable for this type of use case. It will add a few methods to Hash and ActionController::Parameters: #allow, #allow!, #forbid and #forbid!

You would use it like this

params.require(:company_user).permit(:otp_setup).forbid(otp_setup: [true])

# or

params.require(:company_user).permit(:otp_setup).allow(otp_setup: [false])

You can specify a single value or an array of values, and it doesn't mutate the original params object

Upvotes: 12

Chris Peters
Chris Peters

Reputation: 18090

I don't really recommend messing around with the params object in this case. I think it's best to leave that untouched for the most part to preserve what was actually requested. That way you're not left scratching your head if you need that value again somewhere downstream.

Another approach is to build the list of attributes to accept before passing into permit.

# Attributes that everyone can modify.
attrs = [:attrs, :everyone, :can, :modify]

# Then "whitelist" other attributes based on your permission logic.
if curr_company_user.is_sys_admin? && curr_company_user.can_crud_company_users? && params[:id].to_i != curr_company_user.id
  attrs << :otp_set_up unless params[:company_user][:otp_set_up] == true
elsif something_else?
  # Modify what can be permitted for this case.
  # etc...
end

params.require(:company_user).permit(*attrs)

Upvotes: 0

Related Questions