Disable strong parameters for a specific action - ruby-on-rails

I have a serious problem with strong parameters. Its working pretty well in my about 200 actions but in one it doesn't because I'm working very dynamic with the parameters there and I also cant change it because of the applications design.
So I want to disable strong parameters validation in just this specific action. Is there a way to do this?

Strong parameters overrides the params method in ActionController::Base. You can simply override it and set it back to what you want yourself.
So this:
class MyController < ApplicationController
def params
request.parameters
end
end
Will effectively disable strong parameters for all actions in your controller. You only wanted to disable it for a particular action though so you could do that with:
class MyController < ApplicationController
before_action :use_unsafe_params, only: [:particular_action]
def params
#_dangerous_params || super
end
def particular_action
# My params is unsafe
end
def normal_action
# my params is safe
end
private
def use_unsafe_params
#_dangerous_params = request.parameters
end
end

Not too sure if this is best practice but for Rails 5 I just use request.params instead of params anytime I want to skip strong params.
So instead of something like:
post = Post.new(params[:post])
I use:
post = Post.new(request.params[:post])

You can use .permit! to whitelist any keys in a hash.
params.require(:something).permit!
However this should be treated as an extreme code smell and a security risk.
Nested hashes can be whitelisted with this trick:
params.require(:product).permit(:name, data: params[:product][:data].try(:keys))

Related

Whitelist Parameters in Controller without a Model

I would like to whitelist a parameter from my params. I have a controller ElectiveRecommendationsController that isn't tied to any Model.
How do I whitelist my :electives? I tried the following, but it didn't work.
def permitted_params
params.permit(:electives)
end
The error I am getting:
You need to specify that electives is a Hash. You'll want this
def permitted_params
params.permit(electives: {})
end

Required certain params on certain actions

I have a requirement to need to validate presence of some params in certain situations. Here is the example of that :
In my user controller, for update action, I am required to validate the presence of these params. Same deal for car controller, update action as well, you could see recurring theme here. Params are additional_info.
My base controller provides additional_info_params which pulls the right data from the request.
Here is what I tried so far. I created a AR controller concern and included it in the controller, here is some code:
module ClassMethods
def require_additional_info_for(*methods)
binding.pry
return unless methods.include?(action_name)
if additional_info_params.empty?
head 400
end
end
end
My idea was to be able to define methods that require these params on the top of controller file, just like before_action from rails or skip_authorization_check from cancan. Like so:
MyController < BaseController
include Concerns::AdditionalInformation
require_additional_info_for :update
def update
...
end
end
This code above however does not work as I intended, mainly because this fires on the request class without much knowledge about the request (where I need to derive action name from via action_name).
So how can I do something like this?
Yes, you can, but i suggest you to use the before_action callback!
In a 'abstract' controller, register your method like this:
class SameController < ApplicationController
...
protected
def require_additional_params
render status: :unprocessable_entity if additional_info_params.empty?
end
end
After this, all the controllers who will use this methods, must extends SameController, and runs before_action passing the above method for the wanted actions, for example:
class UserController < SameController
before_action :require_additional_params, only: [:action1, :action2]
end
Note: You can put the require_additional_params in a module and include in your controller, or just put it in the ApplicationController
You might also look at making these regular strong params in the respective controller. It looks something like this:
def update_params
params.require(:car).permit(:engine, :wheels, :rims).tap do |car_params|
car_params.require(:engine)
end
end
This would expect a top-level :car key params (which it strips), and require an :engine param, but allow the other 2 (:wheels and :rims). If :engine isn't present, it will raise a ActionController::ParameterMissing (just like if :cars was missing)
This is straight from the action controller strong params docs (last example at bottom)
I'll sometimes throw these into separate private methods on the respective controller, so there would also possibly be a create_params method with different requirements. I prefer this method over using a custom method as a before_action.

How do I pass params to Rails controller's callback

The callback is like below.
class User
include UserSettable
before_action :set_user
def show
[Something with #user]
end
end
class Group
include UserSettable
before_action :set_user
...
end
And the set_user is like below. And this method is defined in the controller concern.
module UserSettable
extend ActiveSupport::Concern
def set_user
#user = User.find(params[:id])
end
end
I have three questions.
How do I pass params to callback method?
Can the instance variable defined in concern be used in controller?
Does this implementation deviate from the usual usage of rails?
How do I pass params to callback method?
params of request will automatically be available to action. You don't need to pass it explicitly. Your code snippet is perfect use case of how it should be used.
Can the instance variable defined in concern be used in controller?
You mean controller's concern. I think the instance variable defined there should be available. Can you give an example of what you are trying to achieve?
Does this implementation deviate from the usual usage of rails?
No

Toggle "ActionController::Parameters.action_on_unpermitted_parameters = :raise" on specific controller methods?

I want to use
ActionController::Parameters.action_on_unpermitted_parameters = :raise
To raise an exception unpermitted parameters are passed in, but I only want to do this on specific controller methods, instead of setting it in /config/ and having it apply to the whole environment. Is there any way to do so?
I'm not sure Wasif's answer will work properly because it never sets it back. At a minimum I think you want this:
class SomeController < ApplicationController
around_action :raise_action_on_unpermitted_parameters, only: %i[create update]
def index
# ...
end
def create
# ...
end
def update
# ...
end
private
def raise_action_on_unpermitted_parameters
begin
ActionController::Parameters.action_on_unpermitted_parameters = :raise
yield
ensure
ActionController::Parameters.action_on_unpermitted_parameters = :log
end
end
end
And even then I'm not sure you won't encounter a race condition where it's set to raise on different controller actions accidentally if you're using a multithreaded server like Puma.
You can create your own Parameters class:
class Parameters < ActionController::Parameters
self.action_on_unpermitted_parameters = :raise
end
def params
Parameters.new(super.to_unsafe_hash)
end
This relies on Rails' current implementation to check self.class.action_on_unpermitted_parameters to determine what to do.
I prefer this solution over toggling ActionController::Parameters.action_on_unpermitted_parameters during the action because you are in control where you want your custom behaviour. Other code is not affected. But it depends on your needs. Do you want the raising behaviour during some controller actions or within the controller action code?
Update: (taking inspiration from Mike's answer)
To restrict the change only for a set of controller actions and to revert it back to the original value that the class attribute was holding before the action being invoked, we can use around_action
class SomeController < ApplicationController
around_action :raise_action_on_unpermitted_parameters, only: %i[create update]
def index
# ...
end
def create
# ...
end
def update
# ...
end
private
def raise_action_on_unpermitted_parameters
original = ActionController::Parameters.action_on_unpermitted_parameters
ActionController::Parameters.action_on_unpermitted_parameters = :raise
yield
ensure
ActionController::Parameters.action_on_unpermitted_parameters = original
end
end

Refactoring editing user in Rails

Im working with a medium sized Rails application and I do this in every controller:
def create
#object = Model.new(params[:model].merge(editing_user: current_user))
...
end
def update
#object = Model.find(params[:id])
#object.editing_user = current_user
...
end
Setting the editing user over and over again is not DRY. I thought about cleaning this up with an observer but it would need access to the current user. Observers do not have access to the current user, neither should they (Law of Demeter).
Any suggestions how to DRY this up between controllers?
class ApplicationController < ActionController::Base
before_filter :init_request
def init_request
params[:editing_user] = current_user
end
end
I like using decent_exposure to dry up my controllers. It automatically finds or initializes a model instance, based on whether an :id was passed as a param, and it assigns the attributes from params[:model].
To finish drying up your code, you could use the new strategy support (see the end of the readme) to automatically set the editing_user attribute on your model.
You could try an after_filter for this. Perhaps something like so:
class ApplicationController < ActionController::Base
after_filter :set_editing_user
def set_editing_user
#object.update_attribute(:editing_user, current_user) if #object && current_user
end
The difficulty, of course, is that you'll be saving the object twice per call. Generally though creations and updates don't happen so frequently that two database commits is a serious problem, but if you expect to be the next Twitter -- with massive database insertion load -- it could be an issue.
You could also possibly set this in a before_filter, but then you'd have to find or set the object in a previous before_filter. Otherwise #object will always be nil and the before_filter will never fire. You can use the filter ordering methods prepend_before_filter and append_before_filter to ensure the correct sequencing of these filters.

Resources