Rails, def or constant to symbol - ruby-on-rails

In my application_controller.rb I have this code:
def current_resource
if admin_signed_in?
:admin
elsif partner_signed_in?
:partner
end
end
Now I want to pass this def to child controllers like this:
authorize_resource current_resource
However it throws me an error. undefined local variable or methodcurrent_resource'`
How I can pass this current_resource to its child controllers as symbol.
That is how I call it inside controller:
class PageController < ApplicationController
authorize_resource current_resource
end
current_resource is inside application_controller

You're attempted to invoke an instance method on the class. You cannot do this.
If you want to invoke the method without an instance, you need to declare it on self:
def self.current_resource
if admin_signed_in?
:admin
elsif partner_signed_in?
:partner
end
end
This will still likely not work, unless each of the methods used inside that method are also declared as class-level methods.

This code:
class PageController < ApplicationController
authorize_resource current_resource
end
will be executed during PageController class loading, not during request handling. So you can't call admin_signed_in? and partner_signed_in? then. Also I don't know why are you trying to call authorize_resource with argument, because it doesn't get arguments, check cancancan source.
I think you have misunderstood how cancan filters and abilities work. You shouldn't pass User model to the filter. User type should be checked in Ability class and based on that, proper permissions should be selected. In your controller (btw. it should be PagesController) you only load resources and authorize them, ie.:
class PagesController < ApplicationController
load_resource
authorize_resource
# or just load_and_authorize_resource
end
You can customize how resources will be loaded and authorized. Please read this about authorizing controller actions and those examples. And also please read this about defining abilities.

Related

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.

Rails Devise: calling super inside authenticate_group! raises no superclass method `authenticate_group!` in Controller

Devise has a handy feature called devise_group (link to documentation), which creates a group with your multiple devise models.
The docs are really self explanatory. If you have 2 devise models named, for instance, Admin and User, you can use devise_group like so:
class ApplicationController < ActionController::Base
devise_group :blogger, contains: [:user, :admin]
end
This would give you, alongside with authenticate_user! and authenticate_admin!, the method authenticate_blogger!, which would redirect unless user or admin were signed in.
We have been using this in production and it works great. We have the flexibility to restrict some Controller/actions to admins using authenticate_admin! and use authenticate_blogger! when we can have both accessing it.
Due to some complex business logic we have, we had to override authenticate_user! in ApplicationController, following this nice StackOverflow answer here.
Basically it proposes to override ApplicationController#authenticate_user! and calling super when we want the flow follow trough Devise's.
The problem arose when we tried to do the same solution with `authenticate_blogger!. If we do this:
class ApplicationController < ActionController::Base
devise_group :blogger, contains: [:user, :admin]
def authenticate_blogger!
super
end
end
// Another controller
class DashboardController < ApplicationController
before_action :authenticate_blogger!
end
Rails raises this error:
super: no superclass method `authenticate_blogger!' for #<DashboardController:0x00007fd453ca5d80> Did you mean? authenticate_user!
Any idea why calling super inside an override of authenticate_user! in ApplicationController works fine, but the same doesn't happen with the devise group equivalent ?
EDIT 1: found out the reason, but could use some help improving the solution
Looking at devise source code, devise_group uses Ruby's class_eval do define instance methods like authenticate_blogger! in the context of the class it was called.
So when we use devise_group inside ApplicationController, it's like we're defining authenticate_blogger! as an instance method in ApplicationController.
That's why when we manually define that method authenticate_blogger! in ApplicationController and call super it raises the exception, because we actually overwrote the same instance method in the same class (ApplicationController) and there's nothing to find up in the ancestor chain.
authenticate_user!, on the other hand, is way up in the ancestor chain in Devise::Controllers::Helpers (I can see it calling ApplicationController.ancestors`.
The hacky-proof-of-concept-fix we did was to create a TempController, define devise_group inside it, and make ApplicationController inherit from it:
class TempController < ActionController::Base
devise_group :advertiser, contains: [:user, :broker]
end
// In application_controller.rb
class ApplicationController < TempController
def authenticate_blogger!
super // this now works since it goes up in the ancestor chain and finds authenticate_blogger in TempController
end
end
Even tough I'm happy with my investigation ... any suggestions on fixing this without actually having to make ApplicationController not inherit ActionController::Base, like it's default in Rails?

undefined method for imported module

I have a controller under app/controllers
require_relative '../../lib/bases_helper'
class BasesController < ApplicationController
include BasesHelper
def index
BasesHelper.available_bases
end
end
I am trying to use a method defined in another module under lib:
module BasesHelper
def available_bases
#bases = Base.all
end
end
When I run my application and access the site, I get an error
undefined method `available_bases' for BasesHelper:Module
I can navigate to the method with my IDE by just clicking on its name. Why doesn't it resolve the method? What am I missing?
Although Junan Chakma answer will work, i will advise against setting it up that way. Its better (and follows Rails conventions) to use a private method in your controller and use a callback (i.e. before_action); for example:
class BasesController < ApplicationController
before_action :set_available_bases, only: [:index]
def index
end
private
def set_available_bases
#bases = Base.all
end
end
This will set up #bases instance variable to be used in your index action and index.html.erb view.
I think you don't need to add BasesHelper to use available_bases method. Just use method name like this
def index
available_bases
end
As you imported BasesHelper module in your controller all methods of BasesHelper will be available in your controller. So you can use those method just by calling(without its module name) its name.
If you want to improve your code quality and follow rails conventions then please check Gerry's answer.
It is because your method available_bases is an instance method of BasesHelper, not a class method. And you are calling it as if it were a class method.
If you want to use available_bases like a class method, extend the class instead of include-ing it.
class BasesController < ApplicationController
extend BasesHelper
...
end

how do you put before filters in modular controllers?

I have several controllers that are in a module:
class SoapTest::DashboardController < ApplicationController
class SoapTest::TestCasesController < ApplicationController
etc.
I want to be able to check if a user has certain permissions for a module, and since I don't have a "parent" controller where the above ones inherit, i thought to put the check in a before filter in applications. But I can't seem to get the module name:
in application controller, i have:
before_filter :check_company_features
def check_company_features
puts controller_name
end
but controller_name just returns "dashboard". I need to get the "SoapTest" clause
Be attention, what you currently call modules actually are namespaces.
The reason why controller_name returns only the class name (and not the fully qualified name) is because Rails explicitly strips the namespaces. You can get them by calling the Ruby #name method on the controller class.
class SoapTest::DashboardController < ApplicationController
before_filter :check_company_features
def check_company_features
puts controller_name
# => "dashboard_controller"
puts self.class.name
# => "SoapTest::DashboardController"
end
end
There are several String inflection methods you can call on the #name to get the formatted version.
However, I strongly encourage you to use a namespaced main controller.
Instead of using
class SoapTest::DashboardController < ApplicationController
you can extend a SoapTest::ApplicationController
class SoapTest::ApplicationController < ApplicationController
before_filter :check_company_features
def check_company_features
# ...
end
end
class SoapTest::DashboardController < SoapTest::ApplicationController
end

how to call a helper method from both view and controller in rails?

I created a helper method for some simple calculation. This helper method will just return an integer. I need the helper in both controllers and views.
Unfortunately, it work well in views but not in controllers. I get the undefined local variable or method error. How can I fix it?
Thanks all
In order to use same methods in both controller and views Add you method in application_controller.rb and make it helper methods.
For example
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
#protect_from_forgery # See ActionController::RequestForgeryProtection for details
helper_method :current_user
def current_user
session[:user]
end
end
Now you can use method current_user in both controllers & views
I use a following solution. Because I think helper's methods shoud be stored in an appropriate helper module.
module TestHelper
def my_helper_method
#something
end
end
class SomeController < ApplicationController
def index
template.my_helper_method
end
end

Resources