Becca Royal-Gordon
Becca Royal-Gordon

Reputation: 17861

Allowing additional parameters for admin accounts

I have a number of models with attributes that ordinary users should not be able to change, but admins should. For example (though this is not my problem domain), normal users should not be able to change a Post's user_id, but administrators should be allowed to do so.

Handling this at the view level is simple enough—I can show or not show fields depending on whether the user is an administrator—but I'm not sure how to handle it in the controller's strong parameter handling. The only solution I can come up with (and the solution offered previously) is to Repeat Yourself, something you try to Don't in Rails:

def post_params
  if admin?
    params.require(:post).permit(:title, :text, :date, :user_id)
  else
    params.require(:post).permit(:title, :text, :date)
 end
end

Is there a better way to handle this?

Upvotes: 1

Views: 562

Answers (3)

Becca Royal-Gordon
Becca Royal-Gordon

Reputation: 17861

I wasn't really happy with any of the previous answers. What I really wanted to do was this:

params.require(:post).permit(:title, :text, :date, user_id: admin?)

So I decided to make that possible. First I tried to patch the Rails core, but they weren't interested in accepting the patch:

This API is delicate, there are several directions in which it could evolve and it can get easily out of hand, inconsistent, or create expectations for extending the extensions... We prefer by now to keep it as it is.

In general, we prefer use cases not directly supported by the API to be addressed by regular programming. In this case, it would be

pkeys = [:title, :body]
pkeys << :author_id if admin?
params.require(:post).permit(*pkeys)

or something in that line (you already knew that was possible of course, but just to illustrate the point with an example).

So I turned it into a gem instead, and I'll have it for as long as I want to keep it. It's a shame this feature won't be part of every future Rails app I write, but at least I'll be able to get it from my Gemfile.

gem 'rails_conditional_params'

Upvotes: 0

Bart Jedrocha
Bart Jedrocha

Reputation: 11570

I don't see anything wrong with your current implementation. That being said, if you wanted to re-use these attribute permissions in a different controller (e.g. an Api::PostsConrtoller), one way to DRY it up would be to extract the code into it's own class. This is the approach Ryan Bates used in the Railscast about Strong Parameters (note: requires Pro account).

# app/models/permitted_params.rb

class PermittedParams < Struct.new(:params, :user)
  def post
    if user && user.admin?
      params.require(:post).permit(:title, :text, :date, :user_id)
    else
      params.require(:post).permit(:title, :text, :date)
    end
  end
end

You can then instantiate this class from within the ApplicationController

# app/controllers/application_controller.rb

def permitted_params
  @permitted_params ||= PermittedParams.new(params, current_user)
end

and then use it in any controller where you need that permission logic without duplicating the logic.

# app/controllers/posts_controller.rb

def update
  @post = Post.find(params[:id])
  if @post.update_attributes(permitted_params.post)
    ...
  else
    ...
  end
end

What's really nice about this solution is that you can also use it to DRY-up your views by slightly modifying the PermittedParams class.

# app/models/permitted_params.rb

class PermittedParams < Struct.new(:params, :user)
  def post
    params.require(:post).permit(*post_attributes)
  end

  def post_attributes
    if user && user.admin?
      [:title, :text, :date, :user_id]
    else
      [:title, :text, :date]
    end
  end
end

and exposing the permitted_params method as a view helper.

# app/controllers/application_controller.rb

def permitted_params
  @permitted_params ||= PermittedParams.new(params, current_user)
end
helper_method :permitted_params

Finally, use it within your view to show/hide the form fields.

# app/views/posts/edit.html.erb

<% if permitted_params.post_attributes.include? :user_id %>
# show the user_id field
<% end %>

Upvotes: 1

Ajay
Ajay

Reputation: 4251

You don't need to go for two different params list just to prevent bad users. Rather you should consider using cancan Gem.

Keep using the single params list uniformly(irrespective of User role).

Upon that to restrict/protect your actions (based on user role) , try using cancan gem.

Define your individual roles authorizations in the ability.rb file.

A sample ability file will look like :

 #ability.rb 

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new

    if user.is?(:Administrator) # Access for Admin user
        can :access, :all
    elsif user.is?(:"Finance Manager") #Access for Finance Manager
        can :access, [:subjects, :researchsubjects, :visits, :sessions]
    elsif user.is?(:"Research Director") # Access for Research Director
    else
      #do something
    end
  end
end

Please check, cancan gem for more info.

Upvotes: 0

Related Questions