How do I call a method from ApplicationController in a rails engine?
We are using ckeditor gem to integrate ckeditor in our rails app. Since we implemented our own authorization system, I need to make a custom ckeditor authorization hook which needs to get the current_user object to decide whether engine controller-action is allowed for this user.
I have current_user defined in mail_app ApplicationController, I want to call it from CKeditor::ApplicationController.
I have included my code here
config/inititializers/ckeditor.rb
Ckeditor.setup do |config|
config.current_user_method do
current_user
end
config.authorize_with :chronus
end
lib/ckeditor/hooks/chronus.rb
module Ckeditor
module Hooks
class ChronusAuthorization
include Ckeditor::Helpers::Controllers
def initialize(controller)
#controller = controller
#controller.extend ControllerExtension
end
def authorize(action, model_object = nil)
raise Authorization::PermissionDenied unless authorized?(action, model_object)
end
def authorized?(action, model_object = nil)
if action
if #controller.current_user_for_chronus.is_admin?
[:index, :create, :destroy].include? action
else
[:create].include? action
end
end
end
private
module ControllerExtension
def current_user_for_chronus
ckeditor_current_user
end
end
end
end
end
Ckeditor::AUTHORIZATION_ADAPTERS[:chronus] = Ckeditor::Hooks::ChronusAuthorization
There is suggestion in rails guide to make the engine's scoped ApplicationController to inherit from the main ApplicationController. But I cannot do that as engine here is an external gem. (Or is there a way to change the inheriting class?)
Related
I am returning to Rails after 5 years out of it and trying to understand the changes. I am going through Ryan Bates' Railscasts looking to update a template I built some years ago and am getting the above error when initializing the permissions class for authentication. (See RC#386 about 18:00 into the playback.)
Rails has changed the before_filter to before_action (makes sense...) and I have the following in the ApplicationController:
class ApplicationController < ActionController::Base
before_action :authorize
delegate :allow?, to: :current_permission
helper_method :allow?
delegate :allow_param?, to: :current_permission
helper_method :allow_param?
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def current_permission
#current_permission ||= Permissions.permission_for(current_user)
end
def current_resource
nil
end
def authorize
if current_permission.allow?(params[:controller], params[:action], current_resource)
current_permission.permit_params! params
else
redirect_to root_url, alert: "Not authorized."
end
end
end
My permissions.rb file has the following:
module Permissions
def self.permission_for(user)
if user.nil?
GuestPermission.new
elsif user.admin?
AdminPermission.new(user)
else
MemberPermission.new(user)
end
end
end
I'm getting the above error: NoMethodError at /undefined method "permission_for" for Permissions:Module from BetterErrors (and Puma). However the method should be defined in the Permissions module; it's right there. Yet somehow, something has changed in Rails that I can't figure out.
I have tried to require the file: nothing.
Any help?
You need to include a module in a class to use it. It then adds functionality to that class, you do not access methods in module itself.
I'm guessing you need to do something like this (not tested):
class ApplicationController < ActionController::Base
include Permissions
before_action :authorize
change your module to:
module Permissions
def permission_for(user)
...
and then permissions_for is available in your controller
def current_permission
#current_permission ||= permission_for(current_user)
end
The only thing that has changed in Rails is how folders and files are included. The lib folder inside your rails app is not autoloaded by default.
I think best practice is now to add a lib folder within the app folder and then put all your modules and other classes in there. They will be included as soon as you restart your server.
I'm learning rails.
I'm build a simple test application, with a simple authentication scheme.
I'm using a user.role field to group the users.
In my Application Helper i have:
module ApplicationHelper
def current_user
if session[:user_id]
#current_user ||= User.find(session[:user_id])
else
#current_user = nil
end
end
def user_identity
current_user.role if current_user
end
end
Now, in my app, i can use current_user in all controllers as expected, but instead user_identity is not visible.
why?
The application_helper is used mainly to access methods in views - I don't believe it's available in a controller.
The reason your 'current_user' method appears to work is that I'm assuming you're using Devise - when you call 'current_user' it is using the Engine's method rather than your own.
To solve this, write out a new module:
module MyHelper
def current_user
if session[:user_id]
#current_user ||= User.find(session[:user_id])
else
#current_user = nil
end
end
def user_identity
current_user.role if current_user
end
end
And in the controller you're using:
class MyController < ApplicationController
include MyHelper
bla bla bla...
end
Any methods defined in MyHelper will now be available in MyController, as we've included the module in the controller
Helper modules are mixed into the view context (the implicit self in your views) - not controllers.
So you can call it from the controller with:
class ThingsController < ApplicationController
# ...
def index
view_context.user_identity
end
end
Or you can include the helper with the helper method:
class ThingsController < ApplicationController
helper :my_helper
def index
user_identity
end
end
But if you're writing a set of authentication methods I wouldn't use a helper in the first place. Helpers are supposed to be aids for the view.
Instead I would create a concern (also a module) and include it in ApplicationController:
# app/controllers/concerns/authenticable.rb
module Authenticable
extend ActiveSupport::Concern
def current_user
#current_user ||= session[:user_id] ? User.find(session[:user_id]) : nil
end
def user_identity
current_user.try(:role)
end
end
class ApplicationController < ActionController::Base
include Authenticable
end
Since the view can access any of the controllers methods this adds the methods to both contexts.
I am trying to access Devise's current_user variable inside a new instance of another controller. Here is my definition of GetsInterfaceController
class GetsInterfaceController < ApplicationController
def select_current_signed_in_user
#signed_in_user_here = current_user
end
end
Then I instantiate a new instance of GetsInterfaceController in ClientsController
class ClientsController < ApplicationController
def get_current_user
#gets_interface_controller = GetsInterfaceController.new
find_signed_in_user = #gets_interface_controller.select_current_signed_in_user
end
end
But I get null error on the #signed_in_user_here = current_user line in GetsInterfaceController when I try this. Anyway to get to the current_user attribute from inside GetsInterfaceController ?
I solved this by moving my code into a Module in lib directory. Works like a charm
current_user is not a variable - it is a helper method. Thus it is already available in all your helpers and views.
Additionally you never instantiate controllers in Rails. The router does that for you.
The only public methods in your controllers should be the actions which respond to HTTP requests.
If you want to reuse a method in several controllers you should be using inheritance, modules (concerns) or helpers. Never by calling a method on another controller.
To call an external service you want to create an API client class:
# adapted from https://github.com/jnunemaker/httparty
require 'httparty'
class StackExchangeClient
include HTTParty
base_uri 'api.stackexchange.com'
def initialize(service, page, user = nil)
#user = user
#options = { query: {site: service, page: page} }
end
def questions
self.class.get("/2.2/questions", #options)
end
def users
self.class.get("/2.2/users", #options)
end
end
Or if you need to call an external service and for example create several models with the data a Service Object:
class SomeService
def initialize(user, client: SomeClient)
#user = user
#client = client # for mocking
end
def call
response = #client.get('/foo')
response.each do |d|
#user.baz << d[:woo]
end
end
end
SomeService.new(current_user).call
I'm attempting to build a Rails Gem somewhere between Devise and CanCan. Not nearly as complex as Devise, but having views and a controller.
I've created a method to be added to the top of any controller of the parent app that needs it almost exactly like Devise's before_action :authenticate_user! and CanCan's load_and_authorize_resource
I need the method to redirect_to a path in my mounted routes if the requirements are not met.
module MyEngine
module ControllerAdditions
extend ActiveSupport::Concern
module ClassMethods
def pin_verified
current_user ||= nil
#pinned = current_user.nil? ? nil : current_user
redirect_to setup_mobiles_path unless #pinned && #pinned.verified?
end
end
end
end
and in my spec/dummy/app/controllers/users_controller.rb
class UsersController < ApplicationController
pin_verified
def index
#users = User.all
end
end
pin_verified is getting called as it's supposed to but I get the following error:
undefined local variable or method `setup_mobiles_path' for UsersController:Class
Any thoughts on how I should be doing this?
==== edit ====
I altered this now to raise a custom exception, but now I need to rescue that exception some how and redirect to the needed path.
def pin_verified
current_user ||= nil
#pinned = current_user.nil? ? nil : current_user
unless #pinned && #pinned.verified?
raise ValidatedPinExpired
end
end
I tried adding this to the ApplicationController of my gem, but it doesn't seem to be hitting Controller at all.
module MyEngine
class ApplicationController < ActionController::Base
rescue_from Exception do |exception|
Rails.logger.info "==== exception: #{exception} ===="
redirect_to setup_mobiles_path
end
end
end
I am looking to write certain methods for processing strings, and other tasks that take place in numerous of my controllers. I know its bad practice to include helpers in your controller, so I was just wondering, where is the best place to put application wide methods used in controllers?
I realize some of you will say to put them in models, but you have to realize that not all my controllers have an associated model. Any and all input would be appreciated.
I tend to put them into helpers. The fact that they are included in views
automatically haven't been a problem for me. You can also place them into
something like app/concerns/ or lib/
I don't like cluttering ApplicationController with private methods
because this often becomes a mess.
Example:
module AuthenticationHelper
def current_user
#current_user # ||= ...
end
def authenticate!
redirect_to new_session_url unless current_user.signed_in?
end
end
module MobileSubdomain
def self.included(controller)
controller.before_filter :set_mobile_format
end
def set_mobile_format
request.format = :mobile if request.subdomain == "m"
end
end
class ApplicationController
include AuthenticationHelper
include MobileSubdomain
end
If you need to use a method in the application scope then I would suggest that you keep those methods inside the application controller and in order to use them inside views.. declare those as helper methods.
For example,
class ApplicationController < ActionController::Base
helper_method :current_user, :some_method
def current_user
#user ||= User.find_by_id(session[:user_id])
end
def some_method
end
end
I would suggest to put them in lib folder. So for example I have:
lib/utils/string_utils
module StringUtils
def foo
...
end
end
class BarController < ActionController::Base
include StringUtils
end
This demonstrates good methodology called Fat model, Thin controller, in this case we are using Mixins instead of Models to separate logic but idea is same. You want your controllers as simple as possible.
It all depends on your needs. I will provide here 2 examples. Both of them are just a custom libraries, located under lib directory.
First example - "custom string processing"
# lib/filters.rb
module Filters
# Converts value to canonical view
def self.phone(value)
# remove all non-digits
clean_value = value.gsub(/\D/, '')
country_codes = configus.phone.country_codes
area_code = configus.phone.defaults.area_code
case clean_value.length
when 7
"#{area_code}#{clean_value}"
when 11
# remove country code only if phone starts with the allowed country code
if country_codes.include?(clean_value[0].to_i)
clean_value[1..-1]
else
clean_value
end
else clean_value
end
end
# usage
# app/api/phones_controller.rb
class Api::PhonesController < Api::ApplicationController
def exists
if params[:q]
clean_value = Filters.phone(params[:q])
...
end
end
end
Second example - helper for flash messages
# lib/flash_helper.rb
module FlashHelper
def flash_translate(key, options = {})
scope = [:flash, :controllers]
scope += params[:controller].split('/')
scope << params[:action]
t(key, {:scope => scope}.merge(options))
end
end
# app/application_controller.rb
class ApplicationController < ActionController::Base
include FlashHelper
end
# usage
# app/your_controller.rb
class YourController < ApplicationController
def create
#object = Object.new(params[:object])
if #object.save
flash[:success] = flash_translate(:success)
...
end
end
end
Note: do not forget to add lib dir to the autoload paths. In config/application.rb add/modify this line config.autoload_paths += %W(#{config.root}/lib).
So for me the answer is lib directory.
Starting from Rails 4 there is a dedicated folder for it app/controllers/concerns. So you can create a module there and then include it in a specific controller(s) or in ApplicationController if you need it available throughout all your controllers.
In case those methods are used in numerous controllers, I would define them in application_controller.rb. Every controller will inherits from it and will be capable to use any method defined there