Required certain params on certain actions - ruby-on-rails

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.

Related

Pundit: Authorize specific resource to differently named controller

Let's say I am using the Pundit gem for authorization. I have the following controller:
class BlogsController < ApplicationController
before_action :check_authorization
...
private
def check_authorization
authorize :blog
end
end
Pundit looks at the argument passed in, :blog and thus infers there is a BlogPolicy class. It then looks for the corresponding action name with a question mark, such as: BlogPolicy#index?.
If I want to look up the authorization on a specific resource I would just do this for that check_authorization method:
def check_authorization
authorize #blog
end
Again, no problem. Pundit looks at the class of the resource, Blog, and then looks for the BlogPolicy class.
Example: The app could call the BlogPolicy#show? method and pass in that #blog resource.
Here is the issue: what do I do when I want to authorize on a specific controller name AND authorize on a specific resource, but that resource does not sync up with the controller name?
Example:
class SomeOtherBlogsController < ApplicationController
before_action :check_authorization
...
private
def check_authorization
authorize :some_other_blog, #blog
end
end
The above code doesn't work, but hopefully it shows what I am trying to do. Lets pretend this is happening on SomeOtherBlogsController#show action.
I am trying to have pundit find the SomeOtherBlogPolicy#show? method,
Within that Policy method, I want to check the authorization access of the #blog resource as well.
Hopefully the issue is apparent. Since the resource class does not sync up with the controller name, It seems I am not able to do the above. So If I have the same resource used in various different controllers, I'm out of luck. I wouldn't be able to check authorization of the resource in those various controller contexts. Is this impossible to do with Pundit?
Update:
In the controller, I have also attempted to directly call the Policy method like so:
SomeOtherBlogPolicy.new(current_user, #blog).show?
However, calling that raised a Pundit::AuthorizationNotPerformedError. So it appears that more happens in that authorize method than just returning true or false.
You can manually specify the resource class for a model by:
class Blog
def self.policy_class
SomeOtherBlogPolicy
end
end
Unfortunately its not possible to specify the policy class from the controller when calling authorize...
This was true when I originally wrote the answer. v2.00 added a policy_class option to authorize:
authorize(#blog, policy_class: SomeOtherBlogPolicy)
So the workaround in my original answer is no longer needed.
This is now a built in feature in Pundit Version 2.0.0. The documentation is updated with the following:
You can pass an argument to override the policy class if necessary. For example:
def create
#publication = find_publication # assume this method returns any model that behaves like a publication
# #publication.class => Post
authorize #publication, policy_class: PublicationPolicy
#publication.publish!
redirect_to #publication
end
Is it possible to override the policy class in the view as well? I tried doing this but got an error unknown keyword: :policy_class
<% if policy(#publication, policy_class: PublicationPolicy).create? %>

Create an "includable" Rails controller action within helper

Is it possible to make an includable controller action within a Rails Helper through an included block? I'm thinking something like this:
module XablauHelper
included do
def my_shared_action
true
end
end
end
Already tried doing it through class.eval block and through using like a class method i.e. self.my_shared_action but no success, I have already found a solution that is making a parent controller with the desired shared actions and inheriting from it, but for the sake of modular design I would like to make it a more "global" approach, so I could gemify my solution and reuse code, any suggestions that doesn't use inheritance?
Adding controller actions in a helper is probably the wrong choice, as these methods are intended for your views.
Consider using controller concerns instead, and including them where required. For example:
# in app/controllers/concerns/useful_functions_concern.rb
module UsefulFunctionsConcern
extend ActiveSupport::Concern
included do
rescue_from SomeException, with: :handle_access_denied
end
def useful_method
# ...
end
protected
def handle_access_denied
# ...
end
end
# in your controller
class XyzController < ApplicationController
include UsefulFunctionsConcern
def index
useful_method
end
end
Where common controller actions can be shared and the controllers have something in common e.g. they are all API controllers, also consider using inheritance to achieve this. For example:
# parent controller
class ApiController < ApplicationController
def my_shared_action
end
end
class SpecificApiController < ApiController
end

How do I build a controller filter override helper

In Rails you can add before_filters to your controllers. Assuming a base controller has the following:
before_filter :my_base_filter
Given this, assume that you have a whole host of controllers that inherit from this base controller, and that the norm is that this behavior is correct. I now have a small handful of controllers that do the following:
skip_before_filter :my_base_filter, only: [:method1, :method2]
before_action only: [:method1, :method2] do
my_secondary_filter(param1)
end
Given that this code is in a few of my controllers, and that the methods that are passed in (as well as whether or not it uses only or except) are different from one controller to the next, I would ideally like to have a single before_action or before_filter that I could call that would effectively run the above code. The call, in the inherited controller, would ideally look something like:
replace_filter(param1, {only: [:method1, :method2]})
The second parameter (the hash detailing which methods to apply it to) should be able to accept an empty value and apply to all methods. I have created a helper function that (is written alongside these other filters and), syntactically and logically should do this, but can't seem to properly invoke it using a before_action or before_filter without my_base_filter executing first. Is it possible to do something similar to this, and if so, what is the best way to do so?
application_controller.rb
class ApplicationController < ActionController::Base
include ApplicationHelper
before_action :my_base_filter
...
end
inherited_controller.rb
class InheritedController < ApplicationController
# I want to replace these lines with my new helper function
skip_before_filter :my_base_filter, only: [:method1, :method2]
before_action only: [:method1, :method2] do
my_secondary_filter(param1)
end
...
end
application_helper.rb
class ApplicationHelper
def my_base_filter
# Do shit here that is the normal behavior
end
def my_secondary_filter(param1)
# Do shit here that is specific to certain functions INSTEAD
# of running the normal base filter
end
# I want to be able to simply call this function
# as a before_action or before_filter in order
# to DRY up my code
def replace_filter(param1, methods = {})
# Run validation on parameters (including methods) here
# including raising exceptions if necessary
...
# Then run the following
skip_before_filter :my_base_filter, methods
before_action(methods) do
my_secondary_filter(param1)
end
end
end
So... there's this thing in Rails called a Concern. It's intended to be the way you pull out modules of stuff in Rails - while allowing a whole bunch of neato things that you can use.
There's a number of articles out there on what they are and how to use them. I'll let you go explore.
I can't say for certain that the following will fix your problem, but it's how I'd approach it.
Part of your problem is that as you say - by the time you get to running your "replace_filter" method, the bas_filter method has already run.
What you need is to be able to run replace_filter on the first time that your ApplicationHelper is included into the Controller.
This is where ActiveSupport::Concern's included method comes to your aid.
Give something like this a try:
# give this a meaningful name...
class FilterStuff < ActiveSupport::Concern
included do
puts "I'm in included"
# note: not the replace_filter, but the method that will call it
if defined?(:replace_filter_method)
puts "This controller defines replace filter method which I'm now calling"
replace_filter_method
else
puts "this controller does not define the replace method and will default to base behaviour"
before_action :my_base_filter
end
end
def my_base_filter
puts "I'm in my base filter"
end
def my_secondary_filter(param1)
puts "I'm in secondary filter with param: #{param1}"
end
# making this work is another problem...
def replace_filter(param1, methods = {})
puts "I'm in replace filter with: #{param1} and #{methods.inspect}"
# Run validation on parameters (including methods) here
# including raising exceptions if necessary
...
# Then run the following
skip_before_filter :my_base_filter, methods
before_action(methods) do
my_secondary_filter(param1)
end
end
end
class InheritedController < ApplicationController
include FilterStuff
# actions go here...
private
# define this method only on controllers that need it
def replace_filter_method
puts "I'm in this controllers replace filter method"
replace_filter(param1, only: [:method1, :method2])
end
end
I've added a whole bunch of printf debugging - have a run trhough and it'll tell you what is being called when - and that will help you determine what you need to do to get the proper stuff working.

How to reuse the rendering actions from nested controllers in Rails?

I have a question regarding the reuse of code among controller actions. I think it is a fairly standard situation, so I am interested in what's the best practice in Rails.
Let's say I have a films resource with a corresponding FilmsController, which has a nested resource comments served by CommentsController. The nested resource can be rendered on its own using its index and show actions. However, it should also be possible to render the comments embedded in the corresponding film page.
Now, the question goes, what is the best way to reuse the code from CommentsController within FilmsController.show?
1) Force the CommentsController.index to render to a string and then pass it in a variable to the film view?
Or 2) call the CommentsController.index directly in the film view as a kind of "partial", executing the database queries from there?
Or 3) create a separate method in CommentsController responsible for the database handling, call it from both CommentsController.index and FilmsController.show, and use the corresponding view in both the places, too?
To me the options 1) and 2) seem a bit messy, while 3) is not modular and involves some repeating of code. Is there any better way to accomplish this?
Thanks a lot!
Now, the question goes, what is the best way to reuse the code from CommentsController within FilmsController.show?
You could move the shared controller logic into a inside your application controller (or a lib and require it appropriately), a la:
class ApplicationController < ActionController::Base
def foo
#foo = "foo"
end
end
Comments Controller:
class CommentsController < ApplicationController
before_filter :foo, :only => [:index]
def index
end
end
Films Controller:
class FilmsController < ApplicationController
before_filter :foo, :only => [:show]
def show
end
end
For repeated view logic you can move that to a common folder, say your_app/app/views/shared/_foo.html.erb and render that appropriately.
Another option is to place the relevant code into an external module:
lib/mymodule.rb
module MyModule
def foo
end
end
And then you can include the module inside your controller or anywhere you want access to your foo method.
class CommentsController < ApplicationController
include MyModule
def index
foo()
end
end

Where should I store common function at?

I have three models.
users_controllers.rb
communities_controller.rb
community_tipics_controller.rb
I'm using the function called check_digit
def checkdigit
(transaction)
end
and I'm placing this in all the controllers.and calling it by before_filter.
But I think I'm wasting because I'm just pasting exactly the same code above to all of the controllers.
Where am I supposed to put it if my controller is using it in common?
Should I put it in controllers/application_controller.rb?
There are several ways of doing this , some possible ways are (If I'm to do this)
1st method
If this is a simple method used by controllers
inside application_controller.rb
private
def checkdigit
(transaction)
end
2nd method
if your function is used by a specific category , (this is normally I do very often), create a separate controller and have your all other controllers inherited by it
Ex: if your method used only my admins, and assuming you have some more methods like that
class AdminController < ApplicationController
layout 'admin'
private
def checkdigit
(transaction)
end
end
and
class UsersController < AdminController
end
3rd method
If your method is/will used by models/controllers etc.. consider making it a module
module CommonMethods
def checkdigit
(transaction)
end
end
class UsersController < ApplicationController
include CommonMethods
end
HTH
You can put it in helpers/application_helper.rb
You are correct, all common methods that all controllers need should be stored in the ApplicationController.
Furthermore, you should also keep common logic between all controllers in this controller.
Edit:
If they are just helpers, then you would put them where the helpers go, see the answer by #simone.

Resources