Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allowing only certain possible values in Rails Strong Parameters

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?

like image 515
Ephraim Avatar asked Feb 05 '15 04:02

Ephraim


1 Answers

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.

like image 114
Ephraim Avatar answered Oct 20 '22 04:10

Ephraim