I'm using Grape to build an API.
I created an ActiveSupport::Concern let's say with the name Authentication and I applied some before filter so my concern looks like:
module Authentication
extend ActiveSupport::Concern
included do
before do
error!('401 Unauthorized', 401) unless authenticated?
end
....
end
end
Now let's say in my UserController I want to apply this concern only for a specific action. How can I do that?
class SocialMessagesController < Grape::API
include Authentication
get '/action_one' do
end
get '/action_two' do
end
end
Any easy way to specify the concern for a specific method just like before_filter in rails with only option?
Separate your actions into different classes and then mount them within a wrapper class:
class SocialMessagesOne < Grape::API
include Authentication
get '/action_one' do
# Subject to the Authentication concern
end
end
class SocialMessagesTwo < Grape::API
get '/action_two' do
# Not subject to the Authentication concern
end
end
class SocialMessagesController < Grape::API
mount SocialMessagesOne
mount SocialMessagesTwo
end
More information on mounting is available in the Grape README.
Related
What is the best and correct approach to do caching for actions?
Am I forced to ActionController::Base?
Is there another way (keeping ActionController::API present)?
Do I have to push caching down to Model layer?
I saw that Rails 6 (maybe prior) does not support action caching out of the box anymore. The caching was extracted to a gem: actionpack-action_caching https://github.com/rails/actionpack-action_caching. I installed it, but it seems not working with ActionController::API, it only works with ActionController::Base.
class ApplicationController < ActionController::API
must be changed to
class ApplicationController < ActionController::Base
Then, and only then I can cache an action like so:
class CategoryController < ApplicationController
caches_action :index
def index
#root_categories = Category.roots
end
end
Thank you in advance
The gem adds these methods to your controllers by including ActionController::Caching in ActionController::Base (see here). To add this behaviour to controllers that don't extend from ActionController::Base, simply include ActionController::Caching.
e.g.
class ApplicationController < ActionController::API
include ActionController::Caching
end
Rails version is 6.0.0.3 and Ruby version is ruby 2.6.6-p146
I need to send the incoming request to the application controller to 2 different concerns based on the request format.
I have an application controller
class ApplicationController < ActionController::Base
include Authentication
include Authorization
before_action :authenticate_or_authorize
private
def authenticate_or_authorize
request.format.json? ? :authorize_request : :authenticate_request
end
end
I have 2 concerns namely authentication and authorization. if the incoming request is of type JSON, I need to call the authorization concern.
If it is a non json request i need to call authenticate_request.
Here are my concerns.
module Authentication
extend ActiveSupport::Concern
included do
before_action :authenticate_request, :return_url
end
private
def authenticate_request
// do something to authenticate
end
end
Here is the second concern.
module Authorization
extend ActiveSupport::Concern
include ::JwtVerifiable
included do
before_action :authorize_request
end
private
def authorize_request
// do something to authorize.
end
end
The current behavior is request always goes to the Authentication concern. Then afterward comes to the authenticate_or_authorize defined in the ApplicationController.
Can anyone help to correct the issue?
Thanks in advance.
The Concerns are already providing their own before_action calls when they are included (that's what included do (block) end is doing). This means that :authenticate_request, :return_url and :authorize_request are going to be run on every request. If you can't remove those from the concerns themselves, you skip them with:
skip_before_action :authenticate_request, :authorize_request
after including the concerns in ApplicationController. The before_action you've defined in ApplicationController will still run.
I wrote this module:
module Hooks
module ExecutionHooks
def before_action(hook, *method_names)
method_names.each do |method_name|
method = method(method_name)
define_singleton_method(method_name) do |*args, &block|
method(hook).call(*args)
method.call(*args, &block)
end
end
end
end
def self.included(base)
base.send(:extend, Hooks::ExecutionHooks)
end
end
This module allows other modules or classes to define a hook which should be called before a particular action similar to a before_action in Rails.
Then I included this module in my HTTParty module:
module HTTParty
include Hooks
before_action :perform_action, :get
def self.perform_action
puts "performed"
end
end
There is a class which includes the HTTParty module:
class TestClient
include HTTParty
...
end
When I try to access the get method in TestClient, it doesn't call perform_action. The get method being included here is original one, not the redefined one.
Is there a way to include the redefined get method in the TestClient class?
Your code almost works, but get isn't actually defined directly on HTTParty, which you didn't expect, and HTTParty's included class method adds get to your class through another path.
HTTParty has a module called HTTParty::ClassMethods that contains get, etc. It puts them in two places: on HTTParty itself so you can call HTTParty.get, and on any class with include HTTParty, via the included hook. When you open up module HTTParty and include Hooks, you're inserting hooks on HTTParty.get, which is a different lookup chain that when you call TestClient.get. Leaving your Hooks::ExecutionHooks module alone, I recommend making a HookedHTTParty module instead of monkeypatching HTTParty. That will make it more clear what's going on and avoid the complexity of HTTParty's internals, which we shouldn't really be fiddling with.
# hooked_httparty.rb
require 'httparty'
require 'hooks'
module HookedHTTParty
module ClassMethods
def global_perform(*args)
puts "Running global perform with args #{args.inspect}"
end
end
def self.included(base)
base.include(HTTParty)
base.include(Hooks)
base.extend(HookedHTTParty::ClassMethods)
base.before_action :global_perform, :get
end
end
This makes sure HTTParty and Hooks are available on base, and then extends it with the global_perform hook on every get. The primary different from your initial code is that before_action gets called on base (TestClient) instead of on HTTParty, so we catch the right get method. You'll also notice global_perform accepts *args, since you're calling it that way when you generate the hook.
Because we include Hooks, you now also have access to before_action in TestClient itself, so you can also define more specific before_actions:
class TestClient
include HookedHTTParty
before_action :local_perform, :get
def self.local_perform(*args)
puts "Running local perform with args #{args.inspect}"
end
end
Running get looks like this:
> TestClient.get 'https://www.stackoverflow.com'
Running local perform with args ["https://www.stackoverflow.com"]
Running global perform with args ["https://www.stackoverflow.com"]
=> #<HTTParty::Response:0x7fa523a009e0 ... >
If you really need anything including HTTParty to get your hooks (perhaps because you don't have control of the thing including it), you might need to monkeypatch HTTParty::ClassMethods directly, since that's the bottleneck where get is defined, but that's getting into even darker territory. As long as you're injecting code around, you could also OtherThing.include(HookedHTTParty) to make it more explicit and keep it just a bit more encapsulated.
I know that in Cucumber that I can create a module and then include that module into the World object and all the methods that I have created within the newly created module are available globally
module MyModule
def my_method
end
end
World(MyModule)
Now anywhere in my cucumber tests I can call my_method and it will work
The issue I see here and an issue I have come across is duplication of method names, as the application gets bigger or other testers/developers work on the application.
So if I was to wrap everything up in its own module and create module methods like so
module MyModule
def self.my_method
page.find('#element')
end
end
World(MyModule)
MyModule.my_method
# This will return undefined variable or method 'page' for MyModule module
So being new to using modules I have read that you can extend other modules so that you can use those methods within another module
So to access the page method I would need to access Capybara::DSL
module MyModule
extend Capybara::DSL
def self.my_method
page.find('#element')
end
end
World(MyModule)
MyModule.my_method now works, but my question is rather than extend individual namespaces for every module that I need access to is there a way to extend/include everything or is this a bad practice?
Another example of where things fail are when I try to access instances of a class
module SiteCss
def login_page
Login.new
end
end
class Login < SitePrism::Page
element :username, "#username"
end
module MyModule
extend Capybara::DSL
def self.my_method
page.find('#element')
login_page.username.set('username')
end
end
World(MyModule)
So with this example if I was it try and call login_page.username I would get
undefined method `login_page`
I'm really looking for the correct way to be doing this.
In conclusion I am trying to understand how to use custom modules and classes in cucumber without having to load everything into the World object.
Yes, it's not pretty to extend a module multiple times, if that's really what you want to do, I can suggest a way you can improve it a bit:
base class which will inherit your page object framework and extend(include is probably the correct option here):
module Pages
class BasePage < SitePrism::Page
extend Capybara::DSL
end
end
Now your login class:
module Pages
class Login < BasePage
element :username, "#username"
def yourmethod
page.find('#element')
username.set('username')
end
end
end
Now the bits you are probably interested in, expose yourmethod to cucumber world:
module SiteCss
def page_object
#page_object ||= Pages::Login.new
end
end
World(SiteCss)
Now you should have access to yourmethod in a cleaner way...
Usage:
#page_object.yourmethod
Although the most important suggestion I could give you is run from SitePrism... Create your own page object framework... don't fall into SitePrism trap...
I'm having a hard time understanding how CanCan works. I have the following model
class Ability
include CanCan::Ability
def initialize(user)
if user && user.email == "jason#gmail.com"
can :access, :rails_admin # only allow admin users to access Rails Admin
can :dashboard # allow access to dashboard
end
end
end
When it comes to my rails_admin file in the initializers folder
RailsAdmin.config do |config|
config.authorize_with :cancan
config.main_app_name = ['Pr', 'Admin']
config.current_user_method { } # auto-generated
end
I want to have one user to access the admins dashboard with the email "jason#gmail.com", but how does CanCan know who is currently signed in at the time? Does it rely on a helper method I'm missing?
CanCan uses a current_ability method to supply the ability, and in that it uses current_user. I know at least Devise has this method, other auth frameworks must commonly supply it too, not sure.