Test Base Controller - ruby-on-rails

I have a SessionsController which all other controllers in my application inherit from. I want to test both base controller methods.
The create rspec works fine, I call "post :create" because there is a route setup. The 2nd method "require_session" is a different story, there is no route. Instead we call this method as before action. What are some approaches I can take to test the require_session method?
class SessionsController < ApplicationController
before_action :require_session
def create
#...
end
def require_session
#...
end
def not_authorized
render json: "Not Authorized", status: 401
end
def server_error
render json: "Server Error (code 1)", status: 500
end
end

How about invoking SessionsController and calling it on that:
sc = SessionsController.new
sc.require_session

Related

Rails action params[:user] not provided how return error

I am working on a project where I want ensure that if request does not have user params it should send error, so I used following method in controller
class UsersController < ApplicationController
before_action :check_params
private
def user_params
params.require(:user).permit(:email, :password)
end
def check_params
render json: { error: 'No user params provided' }, status: 401 unless params[:user]
end
end
It is working fine but I have to put on each individual controller, is there way I can add it in Application for all controllers! as only params[:user] changing, so if I have params[:company] I have to add another method in CompaniesController with params[:company] which is not really dry. I am surprise when we use params.require(:user) why it gives errors instead return validation error.
I look like you want to rescue from ActionController::ParameterMissing with a custom error message. The Rails way to do that is the rescue_from method.
Add the following to your controller:
rescue_from 'ActionController::ParameterMissing' do |exception|
render json: { error: 'No user params provided' }, status: 401
end
Well instead of creating this whole method you can do just check before where your action is checking params[:user] and reject on single line like following
#user = User.find_by_email(params[:user][:email]) if params[:user]
Now it won't search for your params[:user]
You can move common logic into a method in the base class, then derived classes can pass own "changeable" variables to it or ignore a method
class ApplicationController
def check_params(name, error_message)
render json: { error: error_message }, status: 400 if params[name].nil?
end
end
Usage
class UsersController < ApplicationController
before_action -> { check_params(:user, "User is missing") }
end

How can I render a custom JSON error response "Couldn't find Plan with id" in Rails?

I would like to render a custom error that shows when the user cannot find the class 'Plan' by id. The problem is that it's not reaching the if statement.
NB. I am using Insomnia to test it.
class Api::V1::PlansController < Api::V1::BaseController
before_action :authorize_request
def show
#plan = Plan.find(params[:id])
if #plan.errors.any?
render json 'wrong'
else
#accounts = #plan.accounts
render json: #plan, status: :created
end
end
end
ActiveRecord::FinderMethods#find will raise an ActiveRecord::RecordNotFound exception when if one the ids cannot be found. And when an exception is raised it halts execution.
You can handle the exception by using rescue_from:
# Do not use the scope resolution operator when declaring classes and modules
# #see https://github.com/rubocop-hq/ruby-style-guide#namespace-definition
module Api
module V1
class PlansController < BaseController
before_action :authorize_request
rescue_from ActiveRecord::RecordNotFound, with: :not_found
def show
#plan = Plan.find(params[:id])
render json: #plan
end
private
def not_found
render json: { error: 'not found' }, status: :not_found
end
end
end
end
The recommendation to use find_by might sound like a good idea initially until you realize that the exception is really useful as it halts execution of the action and prevents nil errors.
module Api
module V1
class PlansController < BaseController
# ...
before_action :set_plan
def update
# this would create a nil error if we had used 'find_by'
if #plan.update(plan_params)
# ...
else
# ...
end
end
private
def set_plan
#plan = Plan.find(params[:id])
end
end
end
end
Using rescue_from is also a really powerful pattern as it lets you move error handling up in the inheritance chain instead of repeating yourself:
module Api
module V1
class BaseController < ::ActionController::Api
rescue_from ActiveRecord::RecordNotFound, with: :not_found
private
def not_found
render json: { error: 'not found' }, status: :not_found
end
end
end
end
But most likely you don't even need this at all in the first place. Rails rescues ActiveRecord::RecordNotFound on the framework level by sending a 404 - Not Found response. Clients should not need any more context then the status code in this case and returning completely unessicary JSON error message responses is an anti-pattern.

How to handle and dry validations in Rails controllers

I am trying to find an elegant way to handle some shared validations between two controllers.
Example:
I have two Accounts controllers. One to process accounts associations to a user synchronously (using, for instance, a PORO that contains the logic for this case), and another for treating the association asynchronously with a worker. Please assume that the logic differs in each scenario and the fact that being sync/async isn't the only difference.
Then I have this two controllers:
module Accounts
class AssociationsController < ApplicationController
def create
return already_associated_account_error if user_has_some_account_associated?
# action = call some account association PORO
render json: action.response, status: action.status_code
end
private
def user_has_some_account_associated?
params[:accounts].any? { |account_number| user_account_associated?(account_number) }
end
def user_account_associated?(account_number)
current_user.accounts.exists?(number: account_number)
end
def already_associated_account_error
render json: 'You already have associated one or more accounts', status: :unprocessable_entity
end
end
end
Now I have another controller in which I'd want to apply the same validation:
module Accounts
class AsyncAssociationsController < ApplicationController
def create
return already_associated_account_error if user_has_some_account_associated?
# Perform asynchronously some account association WORKER
render json: 'Your request is being processed', status: :ok
end
private
def user_has_some_account_associated?
params[:accounts].any? { |account_number| user_account_associated?(account_number) }
end
def user_account_associated?(account_number)
current_user.accounts.exists?(number: account_number)
end
def already_associated_account_error
render json: 'You already have associated one or more accounts', status: :unprocessable_entity
end
end
end
...
HOW and WHERE could I place the validation logic in ONLY ONE SPOT and use it in both controllers? I think in extracting to a concern at first, but I'm not sure if they are intended for this cases of validation logic only.
For this you should use concerns. It's what's they are designed for.
Under the controllers directory make a concerns directory (if it isn't already there) and inside that make the file association_concern.rb with this content:
module AssociationConcern
extend ActiveSupport::Concern
private
def user_has_some_account_associated?
params[:accounts].any? { |account_number| user_account_associated?(account_number) }
end
def user_account_associated?(account_number)
current_user.accounts.exists?(number: account_number)
end
def already_associated_account_error
render json: 'You already have associated one or more accounts', status: :unprocessable_entity
end
end
Anything that is common to the controllers can go in the concern
Then in your controllers simply include AssociationConcern
class AssociationsController < ApplicationController
include AssociationConcern
def create
return already_associated_account_error if user_has_some_account_associated?
# action = call some account association PORO
render json: action.response, status: action.status_code
end
end
Make them inherit from some new controller and add a before_action, like this:
module Accounts
class AbstractAssociationsController < ApplicationController
before_action :check_for_associated_account, only: [:create]
def check_for_associated_account
if user_has_associated_account?
render json: 'You already have associated one or more accounts', status: :unprocessable_entity
end
end
end
end
module Accounts
class AssociationsController < AbstractAssociationsController
def create
# action = call some account association PORO
render json: action.response, status: action.status_code
end
end
end
Then, depending on whether the logic is really different, you can define the user_has_associated_account? in either this abstract controller, separate controllers or delegate it to some PORO class.

RoR: Why inheritance not working for controller?

I have this following controller for my application:
class Api::BaseApiController< ApplicationController
before_action :parse_request, :authenticate_member_from_token!
def index
render nothing: true, status: 200
end
protected
def authenticate_member_from_token!
if !request.headers[:escambo_token]
#member = Member.find_by_valid_token(:activate, request.headers['escambo_token'])
if !#member
render nothing: true, status: :unauthorized
end
end
end
Then, I have another controller that inherits from that Controller:
class Api::CategoryController < Api::BaseApiController
before_action :find_category, except: [:index]
def index
#category = Category.all
puts(#category)
render json: #category
end
But the controller is allowing requests without the token.
EDIT 1: for some reason the index action started to working normally. But still not doing the validation for the token.
EDIT 2: fixing method from private to protected
Your code needs to render :unauthorized if the token is missing, OR invalid. In other words, you need the code to be along the lines of:
def authenticate_member_from_token!
unless Member.find_by_valid_token(:activate, request.headers['escambo_token'])
render nothing: true, status: :unauthorized
end
end
However, with this code you may find yourself double-rendering in the controller. A cleaner approach could be to instead raise an exception, then rescue from it and render appropriately - e.g.
EscamboTokenInvalid = Class.new(StandardError)
rescue_from EscamboTokenInvalid, with: :escambo_unauthorized
def authenticate_member_from_token!
unless Member.find_by_valid_token(:activate, request.headers['escambo_token'])
raise EscamboTokenInvalid
end
end
def escambo_unauthorized
render nothing: true, status: :unauthorized
end

Rails 3.2.8 - How to get methods form application_controller.rb inside of a module?

I have the following method in my controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
private
def check_api_credential(api_key)
if Credential.find_by_key(api_key)
true
else
false
end
end
end
In all the controllers located directly under controllers folder, this method is reachable.
But the controllers files at controllers/api/v1/photos_controller.rb
module Api
module V1
class PhotosController < ActionController::Base
respond_to :json
def create
redirect_to root_url if check_api_credentials(params[:params][2])
if Photo.create(params[:params][0])
render 'success'
else
render 'failure'
end
end
end
end
end
When I try to save I get
undefined method 'check_api_credentials'
How can I access those methods from application_controllers.rb? They are inside of controllers folder.
class ApplicationController < ActionController::Base
class PhotosController < ActionController::Base
Doesn't ring a bell? All other controllers "directly under controllers folder" are inherited from ApplicationController, but PhotosController is not a child of ApplicationController, it is its sibling. That's why it doesn't see the method.
Is there a reason why you didn't inherit PhotosController from ApplicationController?
In your controller or where ever you need to call the private method in different location, you could try a try: method. So in your code,
def create
redirect_to root_url if try: check_api_credentials(params[:params][2])
if Photo.create(params[:params][0])
render 'success'
else
render 'failure'
end
end
It's might solve. Not sure... Just try to add this and run it....

Resources