Allowing only certain possible values in Rails Strong Parameters
Asked Answered
T

1

6

I have a rails app with a user model, which is able to have several roles. I implemented this using a bitmask like this:

class User < ActiveRecord::Base
  DEFAULT_ROLES = %w[developer entrepreneur]
  ROLES = ['admin', 'entrepreneur', 'developer']

  def has_role?(role)
    roles.include?(role.to_s)
  end

  def is?(role)
    has_role?(role)
  end

  def roles=(roles)
    self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
  end

  def roles
    ROLES.reject do |r|
      ((roles_mask.to_i || 0) & 2**ROLES.index(r)).zero?
    end
  end
end

In the signup page for the app, I want users to choose if they are an 'entrepreneur' or a 'developer'. However, I want to ensure that they are not able assign themselves (or anyone else) any other role, unless they are already an admin.

My first thought was to do this in the roles= method by changin it to look like

  def roles=(roles)
    unless current_user.is?(:admin)
      validates_inclusion_of roles, :in => DEFAULT_ROLES
     end
    self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
  end

However, as I found out, you can't access current_user from inside a model (which I guess makes sense if you think about it...)

My next attempt was to see if I could do this using Strong Parameters.

I was expecting it would look something like this (I'm using devise, overriding the RegistrationsController)

class RegistrationsController < Devise::RegistrationsController

  private
  def sign_up_params
    if (user_signed_in?) && (current_user.is?(:admin))
      params.require(:user).permit(:name, :school, :email, :password, :password_confirmation, {roles: User::ROLES})
    else 
      params.require(:user).permit(:name, :school, :email, :password, :password_confirmation, {roles: User::DEFAULT_ROLES})
    end
  end

  def account_update_params
    if (user_signed_in?) && (current_user.is?(:admin))
      params.require(:user).permit(:name, :school, :email, :password, :password_confirmation, :current_password, :about_me, {roles: User::ROLES})
    else
      params.require(:user).permit(:name, :school, :email, :password, :password_confirmation, :current_password)
    end
  end
end

However, when I tried that, I got this:enter image description here which makes me think I'm misunderstanding how Strong Parameters really works.

Is it possible to restrict what values a user can enter for any given field based on that users's role with Strong Parameters? If not, is there a different way to accomplish this?

Tredecillion answered 5/2, 2015 at 4:38 Comment(4)
It should be defined as model validation. Strong parameter is for filtering param keys, not values.Tortoni
@Tortoni - How can I validate it in the model if I can't access the role current user? (since admins may be modifying other users, not just themselves)Tredecillion
You can not define strong paramter as before_action. Use like this in create or update method. Account.new(account_update_params)Tortoni
Controller (more or less) decides who can do what (and where you go after a particular action). "You're this user/kind of user, can you do what you requested?" If not, go here. If ok, then the model takes care of validating the inputs to see if it's a legal set of values. Controller could enforce a params check (if current user is admin, allow any value for role in update, otherwise, strip that key/value from params.Sociality
T
7

I figured it out, here's how I did it. (This is the method overrides Devise's RegistrationController, if you're not using devise, then simply replace whatever method controls what parameters are entered into a new user.)

class RegistrationsController < Devise::RegistrationsController

 private
  def sign_up_params
    parameters = params.require(:user).permit(:name, :school, :email, :password, :password_confirmation, :about_me, roles: [])
    unless (user_signed_in?) && (current_user.is?(:admin))
      parameters[:roles] = parameters[:roles].reject { |h| !User::DEFAULT_ROLES.include? h }
      parameters
    end
  end


  def account_update_params
    if can? :assign_roles, :all
      params.require(:user).permit(:name, :school, :email, :password, :password_confirmation, :current_password, :about_me, roles: [])
    else
      params.require(:user).permit(:name, :school, :email, :password, :password_confirmation, :current_password)
    end
  end

end

I just filtered out the parameter in parameters[:roles] to only include values that were contained in User::DEFAULT_ROLES (shown above in the question), and then returned the parameters object.

Tredecillion answered 5/2, 2015 at 20:56 Comment(1)
Yup, params are modifiable (you got it sorted before I could get back to you).Sociality

© 2022 - 2024 — McMap. All rights reserved.