Reputation: 17861
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
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
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
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