Rails route path inside as hash value - ruby-on-rails

I'm trying to define constant REDIRECT_DEFINITIONS with application paths as a key. This constant is inside of shared controller:
# frozen_string_literal: true
class SignupBaseController < ApplicationController
REDIRECT_DEFINITIONS = {
test_results: {
new: edit_users_experience_level_path(#current_user),
create: edit_users_experience_level_path(#current_user),
},
experience_levels: {
edit: new_users_identity_check_path,
},
"users/identity_checks": {
new: root_path,
},
default: request.referrer || root_path,
}.freeze
But I'm getting an error:
undefined method `edit_users_experience_level_path' for SignupBaseController:Class
EDIT:
routes.rb:
namespace :users do
resource :dashboard, only: :show
resource :identity_check, only: %i[new create show]
resources :experience_levels, only: %i[edit update]
end

Include url_helpers in controllers as below, so that you can have access to all the routes/url of the application.
# frozen_string_literal: true
class SignupBaseController < ApplicationController
include Rails.application.routes.url_helpers
REDIRECT_DEFINITIONS = {
test_results: {
new: edit_users_experience_level_path(#current_user),
create: edit_users_experience_level_path(#current_user),
},
experience_levels: {
edit: new_users_identity_check_path,
},
"users/identity_checks": {
new: root_path,
},
default: request.referrer || root_path,
}.freeze

Related

How to successfully logout using a Rails 6 API with devise and JWT

I've been stuck for days and searching but I cannot find a correct solution to logout from a devise session using JWT. I had a front made with react and everything works fine on login and searching, but when I logout the page if I don't make a refresh I can't login. I leave the code from devise session controller along side the application controller, route and my middeware build to use redux with my front (I'm working with React to). Thanks in advance and I you need something else, let me know.
Devise::SessionsController
# frozen_string_literal: true
class Api::SessionsController < Devise::SessionsController
respond_to :json, :html
# GET /resource/sign_in
# def new
# super
# end
# POST /resource/sign_in
# def create
# super
# end
# DELETE /resource/sign_out
# def destroy
# super
# end
# protected
private
def revoke_token(token)
# Decode JWT to get jti and exp values.
begin
secret = Rails.application.credentials.jwt_secret
jti = JWT.decode(token, secret, true, algorithm: 'HS256', verify_jti: true)[0]['jti']
exp = JWT.decode(token, secret, true, algorithm: 'HS256')[0]['exp']
user = User.find(JWT.decode(token, secret, true, algorithm: 'HS256')[0]['sub'])
sign_out user
# Add record to blacklist.
time_now = Time.zone.now.to_s.split(" UTC")[0]
sql_blacklist_jwt = "INSERT INTO jwt_blacklist (jti, exp, created_at, updated_at) VALUES ('#{ jti }', '#{ Time.at(exp) }', '#{time_now}', '#{time_now}');"
ActiveRecord::Base.connection.execute(sql_blacklist_jwt)
rescue JWT::ExpiredSignature, JWT::VerificationError, JWT::DecodeError
head :unauthorized
end
end
def respond_with(resource, _opts = {})
render json: resource
end
def respond_to_on_destroy
token = request.headers['Authorization'].split("Bearer ")[1]
revoke_token(token)
request.delete_header('Authorization')
render json: :ok
end
end
ApplicationController
class ApplicationController < ActionController::API
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :authenticate_user
protected
def configure_permitted_parameters
added_attrs = %i[username email password password_confirmation remember_me]
devise_parameter_sanitizer.permit(:sign_up, keys: added_attrs)
devise_parameter_sanitizer.permit(:account_update, keys: added_attrs)
end
private
def authenticate_user
if request.headers['Authorization'].present?
token = request.headers['Authorization'].split("Bearer ")[1]
begin
jwt_payload = JWT.decode(token, Rails.application.credentials.jwt_secret).first
#current_user_id = jwt_payload['sub']
rescue JWT::ExpiredSignature, JWT::VerificationError, JWT::DecodeError
head :unauthorized
end
end
end
def authenticate_user!(options = {})
head :unauthorized unless signed_in?
end
def current_user
#current_user ||= super || User.find(#current_user_id)
end
def signed_in?
#current_user_id.present?
end
end
routes.rb
Rails.application.routes.draw do
devise_for :users, skip: %i[registrations sessions passwords]
namespace :api do
devise_scope :user do
post 'signup', to: 'registrations#create'
post 'login', to: 'sessions#create'
delete 'logout', to: 'sessions#destroy'
get 'login', to: 'sessions#create'
end
resources :notes
resources :searches
get 'get_places', to: 'searches#get_places'
end
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
middleware.js
import * as constants from './constants';
import axios from 'axios';
import { logoutUser } from './actions/authActionCreators'
export const apiMiddleware = ({ dispatch, getState }) => next => action => {
if (action.type !== constants.API) return next(action);
dispatch({ type: constants.TOGGLE_LOADER });
const BASE_URL = 'http://localhost:3001';
const AUTH_TOKEN = getState().user.token;
if (AUTH_TOKEN)
axios.defaults.headers.common['Authorization'] = `Bearer ${AUTH_TOKEN}`;
const { url, method, success, data, postProcessSuccess, postProcessError } = action.payload;
console.log('AUTH_TOKEN '+AUTH_TOKEN);
console.log('url '+url);
axios({
method,
url: BASE_URL + url,
data: data ? data : null,
headers: {
'Content-Type': 'application/json', 'Accept': '*/*'
}
}).then((response) => {
dispatch({ type: constants.TOGGLE_LOADER });
if (success) dispatch(success(response));
if (postProcessSuccess) postProcessSuccess(response);
}).catch(error => {
dispatch({ type: constants.TOGGLE_LOADER });
if (typeof(error.response) === "undefined") {
console.warn(error);
postProcessError('An error has ocurred');
} else {
if (error.response && error.response.status === 403)
dispatch(logoutUser());
if (error.response.data.message) {
if (postProcessError) postProcessError(error.reponse.data.message);
}
}
})
};

RubySpec to test a redirect in Ruby controller

I'm trying to code a spec for a method who redirect to a specific path when the user access to provider/plans and provider/prospects
I was trying with many path combinations in the before group, i was trying with this.
before { get :provider_plans_index_path }
before { get :provider_index_plans_path}
before { get :provider_plans_path}
before { get :provider_index_path}
before { get :provider_plans_path}
before { get :provider_planes_path}
base_controller.rb
class Provider::BaseController < ActionController::Base
layout 'provider'
before_action :allowed_pages
def allowed_pages
redirect_to financial_dashboard_path if !requested_action?(params[:controller])
end
def requested_action?(data)
regexp = %r{
^(provider/plans)|
(provider/prospects)$
}x
data.match?(regexp)
end
end
base_controller_spec.rb
require 'rails_helper'
describe Provider::BaseController, type: :controller do
let(:provider) { create(:provider) }
let(:financial) { create(:financial, provider: provider) }
let(:user) { provider.user }
before { login_user user }
describe 'GET plans' do
context 'with not allowed url' do
before { get :provider_planes_path}
it { should redirect_to financial_dashboard_path}
end
end
end
routes.rb
namespace :provider do
get '', to: 'dashboard#index'
get 'dashboard', to: 'dashboard#index'
resources :plans, only: [:index, :create, :update], path: 'planes'
resources :prospects, only: [:index, :show, :create, :update], path: 'prospectos' do
get 'diagnostico', to: 'prospects#show', on: :member, as: :general
patch 'diagnostico', to: 'prospects#update', on: :member
get 'configuracion', to: 'prospects#configuration', on: :member, as: :configuration
end
end
I'm getting this error with all the combinations
ActionController::UrlGenerationError:
No route matches
For me the solution was call the action in spec via another controller
context 'with not allowed url' do
before do
#controller = Provider::PlansController.new
get :index
end
it { should redirect_to financial_dashboard_path }
end

How can I receive a JSON:API post of nested attributes in Ruby on Rails?

Is there a “standard” approach to receiving (potentially nested) a JSON:API POST object in Rails?
The JSON:API spec uses the same format for GET / POST / PUT, etc, but rails seems to need *_attributes and accepts_nested_attributes_for. These seem incompatible.
I feel like what I'm doing must be somewhat common, yet I'm having trouble finding documentation. I'm wanting to use a React/Redux app that communicates with a Rails app using the JSON:API spec. I'm just not sure how to handle the nested associations.
You can active_model_serializer gem's Deserialization functionality.
From the docs of the gem:
class PostsController < ActionController::Base
def create
Post.create(create_params)
end
def create_params
ActiveModelSerializers::Deserialization.jsonapi_parse(params, only: [:title, :content, :author])
end
end
The above can work with the below JSON API payload:
document = {
'data' => {
'id' => 1,
'type' => 'post',
'attributes' => {
'title' => 'Title 1',
'date' => '2015-12-20'
},
'relationships' => {
'author' => {
'data' => {
'type' => 'user',
'id' => '2'
}
},
'second_author' => {
'data' => nil
},
'comments' => {
'data' => [{
'type' => 'comment',
'id' => '3'
},{
'type' => 'comment',
'id' => '4'
}]
}
}
}
}
The entire document can be parsed without specifying any options:
ActiveModelSerializers::Deserialization.jsonapi_parse(document)
#=>
# {
# title: 'Title 1',
# date: '2015-12-20',
# author_id: 2,
# second_author_id: nil
# comment_ids: [3, 4]
# }
I saw these longs threads/issues discussions #979 and #795 on JSON:API repo days ago, that aparently seems that JSON API don't have a true solution for accepts_nested_attributes_for.
I don't know if it is the better solution but the work around to that is disposing a route to your belongs_to and has_many/has_one associations.
Something like that:
Your routes.rb:
Rails.application.routes.draw do
resources :contacts do
resource :kind, only: [:show]
resource :kind, only: [:show], path: 'relationships/kind'
resource :phones, only: [:show]
resource :phones, only: [:show], path: 'relationships/phones'
resource :phone, only: [:update, :create, :destroy]
# These relationships routes is merely a suggestion of a best practice
resource :phone, only: [:update, :create, :destroy], path: 'relationships/phone'
resource :address, only: [:show, :update, :create, :destroy]
resource :address, only: [:show, :update, :create, :destroy], path: 'relationships/address'
end
root 'contacts#index'
end
Them implement your controllers.
The phones_controller.rb following the example above:
class PhonesController < ApplicationController
before_action :set_contacts
def update
phone = Phone.find(phone_params[:id])
if phone.update(phone_params)
render json: #contact.phones, status: :created, location: contact_phones_url(#contact.id)
else
render json: #contact.errors, status: :unprocessable_entity
end
end
# DELETE /contacts/1/phone
def destroy
phone = Phone.find(phone_params[:id])
phone.destroy
end
# POST contacts/1/phone
def create
#contact.phones << Phone.new(phone_params)
if #contact.save
render json: #contact.phones, status: :created, location: contact_phones_url(#contact.id)
else
render json: #contact.errors, status: :unprocessable_entity
end
end
# GET /contacts/1/phones
def show
render json: #contact.phones
end
private
# Use callbacks to share common setup or constraints between actions.
def set_contacts
#contact = Contact.find(params[:contact_id])
end
def phone_params
ActiveModelSerializers::Deserialization.jsonapi_parse(params)
end
end
By doing that you should be able to request a Phone POST normally through a contact separately, like so:
POST on http://localhost:3000/contacts/1/phone with the body:
{
"data": {
"type": "phones",
"attributes": {
"number": "(+55) 91111.2222"
}
}
}
The response or GET on http://localhost:3000/contacts/1/phones:
{
"data": [
{
"id": "40",
"type": "phones",
"attributes": {
"number": "(55) 91111.2222"
},
"relationships": {
"contact": {
"data": {
"id": "1",
"type": "contacts"
},
"links": {
"related": "http://localhost:3000/contacts/1"
}
}
}
}
]
}
Hope that answer

UI Router and Rails: Template is Missing

I am creating an application with Rails and Angular. I am using ui.router. When I visit a route via a ui-sref link, it loads. However, if I refresh that page, I get the following error:
Missing template posts/show, application/show with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :jbuilder]}.
The home page (root of the site) does not have this problem. Also, if I type in a url, say /posts/1, I will get the same message, but if I click a ui-sref link for that post, the page will load. (Though refreshing that page will break it.) Another behavior that might shed light on the misconfiguration is that I can click a ui-sref link to get to the route /login – this route is undefined on the rails side, but defined in my angular routes (app.js). However, when I try to reach /login by any other means, or if I refresh /login, I get the error: 'No route matches [GET] "/login"'
routes.rb
Rails.application.routes.draw do
devise_for :users, :skip => [:registrations]
root to: 'application#angular'
resources :posts, only: [:create, :index, :show, :update, :destroy] do
resources :comments, only: [:show, :create] do
member do
put '/upvote' => 'comments#upvote'
put '/downvote' => 'comments#downvote'
end
end
member do
put '/upvote' => 'posts#upvote'
put '/downvote' => 'posts#downvote'
end
end
resources :users, only: [:show], to: 'users#show', via: 'get', layout: false
end
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
respond_to :json, :html
before_action :configure_permitted_parameters, if: :devise_controller?
def angular
render 'layouts/application'
end
private
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << :username
end
end
posts_controller.rb
class PostsController < ApplicationController
def show
respond_with Post.find(params[:id])
end
end
app.js
angular.module('appName', ['ui.router', 'templates', 'Devise', 'ui.bootstrap', 'angularMoment'])
.config([
'$stateProvider',
'$urlRouterProvider',
'$locationProvider',
function($stateProvider, $urlRouterProvider, $locationProvider) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
$stateProvider
.state('home', {
url: '/',
templateUrl: 'home/_home.html',
controller: 'MainCtrl',
resolve: {
postPromise: ['posts', function(posts){
return posts.getAll();
}]
}
})
.state('posts', {
url: '/posts/{id}',
templateUrl: 'posts/_posts.html',
controller: 'PostsCtrl',
resolve: {
post: ['$stateParams', 'posts', function($stateParams, posts) {
return posts.get($stateParams.id);
}]
}
})
//more stuff here
.state('login', {
url: '/login',
templateUrl: 'auth/_login.html',
controller: 'AuthCtrl',
onEnter: ['$state', 'Auth', function($state, Auth) {
Auth.currentUser().then(function (){
$state.go('home');
})
}]
})
.state('register', {
url: '/register',
templateUrl: 'auth/_register.html',
controller: 'AuthCtrl',
onEnter: ['$state', 'Auth', function($state, Auth) {
Auth.currentUser().then(function (){
$state.go('home');
})
}]
});
$urlRouterProvider.otherwise('home')
}])
posts.rb (factory)
angular.module('appName')
.factory('posts', [
'$http',
function($http){
var o = {
posts: [],
};
o.get = function(id) {
return $http.get('/posts/' + id + '.json').then(function(res){
return res.data;
});
return o;
}])
My guess is that something needs to be changed in the configuration on the Angular side.

No Route Found Error in custom rails params

I'm working on an application where I've had to put together some custom rails parameters for the routes and I keep getting no route found errors when I try to access the page associated with the show method. The application is allowing me to reach my edit pages, so I know it's working on some level but I have to have an error I'm not seeing somewhere that's messing with the normal view. The custom parameters rely on an :identifier that has been custom created for each object. Because the application manages several institutions, all with their objects and files, I've had to right several different sets of routes to handle each different thing. The routes for institutions seem to be working fine, but the second set, for :intellectual_objects are the ones that aren't working.
This is my routes file (irrelevant parts excluded):
Fluctus::Application.routes.draw do
get "institutions/:institution_identifier/objects", to: 'intellectual_objects#index', as: :institution_intellectual_objects, :constraints => { :institution_identifier => /[\w+\.]+/ }
post "institutions/:institution_identifier/objects", to: 'intellectual_objects#create', :constraints => { :institution_identifier => /[\w+\.]+/ }
#Intellectual Object Routes
#get "objects/:institution_identifier", to: 'intellectual_objects#index', as: :institution_intellectual_objects, :constraints => { :institution_identifier => /[\w+\.]+/ }
#post "objects/:institution_identifier", to: 'intellectual_objects#create', :constraints => { :institution_identifier => /[\w+\.]+/ }
patch "objects/:intellectual_object_identifier", to: 'intellectual_objects#update', :constraints => { :intellectual_object_identifier => /[\w+\/\.]+/ }
put "objects/:intellectual_object_identifier", to: 'intellectual_objects#update', :constraints => { :intellectual_object_identifier => /[\w+\/\.]+/ }
delete "objects/:intellectual_object_identifier", to: 'intellectual_objects#destroy', :constraints => { :intellectual_object_identifier => /[\w+\/\.]+/ }
get "objects/:intellectual_object_identifier/edit", to: 'intellectual_objects#edit', as: :edit_intellectual_object, :constraints => { :intellectual_object_identifier => /[\w+\/\.]+/ }
get "objects/:intellectual_object_identifier/events", to: 'events#index', as: :intellectual_object_events, :constraints => { :intellectual_object_identifier => /[\w+\/\.]+/ }
post "objects/:intellectual_object_identifier/events", to: 'events#create', :constraints => { :intellectual_object_identifier => /[\w+\/\.]+/ }
get "objects/:intellectual_object_identifier", to: 'intellectual_objects#show', as: :intellectual_object, :constraints => { :intellectual_object_identifier => /[\w+\/\.]+/ }
#post "objects/institution_identifier/:intellectual_object_identifier/data", to: 'generic_files#create', as: intellectual_object_generic_files, :constraints => { [:intellectual_object_identifier, :institution_identifier] => /[\w+\.]/ }
#patch "objects/institution_identifier/:intellectual_object_identifier/data/:filename", to: 'generic_files#update', :constraints => { [:intellectual_object_identifier, :institution_identifier] => /[\w+\.]/ }
Blacklight.add_routes(self)
mount Hydra::RoleManagement::Engine => '/'
root :to => "catalog#index"
end
This is my IntellectualObject Controller:
class IntellectualObjectsController < ApplicationController
before_filter :authenticate_user!
#load_and_authorize_resource :institution, only: [:index, :create]
load_and_authorize_resource :through => :institution, only: :create
#load_and_authorize_resource except: [:index, :create]
before_filter :set_object, only: [:show, :edit, :update, :destroy]
before_filter :set_institution, only: [:index, :create]
include Aptrust::GatedSearch
apply_catalog_search_params
include RecordsControllerBehavior
self.solr_search_params_logic += [:for_selected_institution]
def update
if params[:counter]
# They are just updating the search counter
search_session[:counter] = params[:counter]
redirect_to :action => "show", :status => 303
else
# They are updating a record. Use the method defined in RecordsControllerBehavior
super
end
end
def destroy
resource.soft_delete
respond_to do |format|
format.json { head :no_content }
format.html {
flash[:notice] = "Delete job has been queued for object: #{resource.title}"
redirect_to root_path
}
end
end
protected
# Override Hydra-editor to redirect to an alternate location after create
def redirect_after_update
intellectual_object_path(resource)
end
def self.cancan_resource_class
CanCan::ControllerResource
end
private
def for_selected_institution(solr_parameters, user_parameters)
#puts "In for_selected_institution------------------------------------------"
#puts params[:institution_identifier]
#puts params[:intellectual_object_identifier]
if(params[:institution_identifier])
institution = Institution.where(desc_metadata__institution_identifier_tesim: params[:institution_identifier]).first
else
io = IntellectualObject.where(desc_metadata__intellectual_object_identifier_tesim: params[:intellectual_object_identifier]).first
institution = io.institution
end
#puts "INSTITUTION: #{institution.id}"
solr_parameters[:fq] ||= []
solr_parameters[:fq] << ActiveFedora::SolrService.construct_query_for_rel(is_part_of: "info:fedora/#{institution.id}")
end
# Override Blacklight so that it has the "institution_identifier" set even when we're on a show page (e.g. /objects/foo:123)
def search_action_url options = {}
institution_intellectual_objects_path(params[:institution_identifier] || #intellectual_object.institution.institution_identifier)
end
def set_institution
if params[:institution_identifier].nil? || Institution.where(desc_metadata__institution_identifier_tesim: params[:institution_identifier]).empty?
redirect_to root_url
flash[:alert] = "Sonething wrong with institution_identifier."
else
#institution = Institution.where(desc_metadata__institution_identifier_tesim: params[:institution_identifier]).first
authorize! [:create, :index], #institution if cannot? :read, #institution
end
end
def set_object
if params[:intellectual_object_identifier].nil? || IntellectualObject.where(desc_metadata__intellectual_object_identifier_tesim: params[:intellectual_object_identifier]).empty?
redirect_to root_url
flash[:alert] = "Something wrong with intellectual_object_identifier."
else
io_options = IntellectualObject.where(desc_metadata__intellectual_object_identifier_tesim: params[:intellectual_object_identifier])
io_options.each do |io|
if params[:intellectual_object_identifier] == io.intellectual_object_identifier
#intellectual_object = io
#institution = #intellectual_object.institution
end
end
if #intellectual_object.nil?
redirect_to root_url
flash[:alert] = "The object you requested does not exist."
end
#authorize! [:show, :edit, :update, :destroy], #institution if cannot? :read, #institution
end
end
end
I'm getting the following error when I try to access the show route (for example: localhost:3000/objects/test.org/126939282):
ActionController::UrlGenerationError in IntellectualObjects#show
Showing /Users/kec6en/HydraApp/fluctus/app/views/intellectual_objects/_facet_limit.html.erb where line #11 raised:
No route matches {:action=>"index", :intellectual_object_identifier=>"columbia.edu/798d6e812041532c", :controller=>"intellectual_objects", :f=>{"institution_name_ssi"=>["Columbia University"]}}
The parameters are showing:
{"intellectual_object_identifier"=>"columbia.edu/798d6e812041532c"}
And I'm getting this error when I run my spec tests on the IntellectualObjectController
Failure/Error: get :show, intellectual_object_identifier: obj1
ActionController::UrlGenerationError:
No route matches {:intellectual_object_identifier=>"kaulkedurgan.org13/39b1eb47-da8b-4145-b03b-5f1851407012", :controller=>"intellectual_objects", :action=>"show"}
I just don't understand because the routes are there, and some of them appear to be working in the application, but every single one is failing in my spec tests. Any and all help is appreciated. Thank you.
Your route to intellectual_objects#index has the constraint that the :institution_identifier should match /[\w+\.]+/, but columbia.edu/798d6e812041532c does not match that regexp. Even when you add \/ to your regexp, I am pretty sure that the slash will confuse Rails routing system.
Perhaps you want to change the route to something like this
get "institutions/:institution_identifier/:some_id/objects",
to: 'intellectual_objects#index',
as: :institution_intellectual_objects,
constraints: { institution_identifier: /[\w+\.]+/ }
And than provide columbia.edu (institution_identifier) and 798d6e812041532c (some_id) as separate values.
According to your error:
No route matches {:action=>"index"
It seems you're trying to access the index action?
Being honest, I couldn't bring myself to look through all your routes (you may want to cut out the irrelevant stuff?)
How are you calling the routes with link_to?

Resources