Rspec: "No route matches" on scoped api route - ruby-on-rails

I've got problem with RSpec and scoped route. Route looks like this
namespace :api do
scope module: :v1, constrains: ApiConstraints.new(version: 1, default: true) do
resources :users, except:[:new]
end
end
and spec (spec/controllers/api/v1/users_controller_spec.rb) looks like this:
RSpec.describe Api::V1::UsersController, type: :controller do
let(:current_user) { FactoryGirl.create(:user) }
before do
allow(controller).to receive(:authenticate_request!).and_return(true)
end
describe 'GET #show' do
it 'should show user details' do
get api_user_path(current_user.id.to_s), nil, { 'Accept' => 'application/app.com; version=1' }
expect(response).to have_http_status(:success)
end
end
end
I'm getting this error:
ActionController::UrlGenerationError:
No route matches {:action=>"/api/users/57c6a615fd32bd11444f792f", :controller=>"api/v1/users"}
I've tried "api_user_url", :show and others but I think that the problem lays in that scope. My routes looks like this:
api_users GET /api/users(.:format) api/v1/users#index {:constrains=>#<ApiConstraints:0x007fe6824dce50 #version=1, #default=true>}
POST /api/users(.:format) api/v1/users#create {:constrains=>#<ApiConstraints:0x007fe6824dce50 #version=1, #default=true>}
edit_api_user GET /api/users/:id/edit(.:format) api/v1/users#edit {:constrains=>#<ApiConstraints:0x007fe6824dce50 #version=1, #default=true>}
api_user GET /api/users/:id(.:format) api/v1/users#show {:constrains=>#<ApiConstraints:0x007fe6824dce50 #version=1, #default=true>}
PATCH /api/users/:id(.:format) api/v1/users#update {:constrains=>#<ApiConstraints:0x007fe6824dce50 #version=1, #default=true>}
PUT /api/users/:id(.:format) api/v1/users#update {:constrains=>#<ApiConstraints:0x007fe6824dce50 #version=1, #default=true>}
DELETE /api/users/:id(.:format) api/v1/users#destroy {:constrains=>#<ApiConstraints:0x007fe6824dce50 #version=1, #default=true>}

The problem was in a typo 'constrains' in routes file:
scope module: :v1, constrains: ApiConstraints.new(version: 1, default: true) do
Should be 'constraints', only t missing.

Related

Rails API - LoadError in API::V1::UsersController#index

I'm trying to add a simple JSON API to my APP
#routes.rb
namespace :api, :format => :json do
namespace :v1 do
resources :users
end
end
#controllers/api/v1/users_controller.rb
class Api::V1::UsersController < ApplicationController
respond_to :json
def index
respond_with User.all
end
end
#inflections.rb
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'API'
end
rake routes gives me the following:
api_v1_users GET /api/v1/users(.:format) api/v1/users#index
POST /api/v1/users(.:format) api/v1/users#create
new_api_v1_user GET /api/v1/users/new(.:format) api/v1/users#new
edit_api_v1_user GET /api/v1/users/:id/edit(.:format) api/v1/users#edit
api_v1_user GET /api/v1/users/:id(.:format) api/v1/users#show
PATCH /api/v1/users/:id(.:format) api/v1/users#update
PUT /api/v1/users/:id(.:format) api/v1/users#update
DELETE /api/v1/users/:id(.:format) api/v1/users#destroy
When I visit http://localhost:3000/api/v1/users I got the following error:
LoadError in API::V1::UsersController#index Unable to autoload
constant API::V1::UsersController, expected
/path/to/app/controllers/api/v1/users_controller.rb to define it
The error message says exactly what to do. Make sure you use API instead of Api in your controller name.
# == HERE ==
class API::V1::UsersController < ApplicationController
respond_to :json
def index
respond_with User.all
end
end
Or second way declare another acronym, or remove it anyway:
inflections.rb:
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'Api'
end

ActionController::UrlGenerationError: No route matches action and controller

I couldn't find a solution in the other relative questions, so I'm asking my own.
The problem is pretty straightforward. This is the error I'm getting:
Failure/Error: get 'api/v2/special_keys#show'
ActionController::UrlGenerationError:
No route matches {:action=>"api/v2/special_keys#show", :controller=>"api/v2/special_keys"}
This is my routes.rb:
resources :special_keys, only: [] do
collection do
get '', to: 'special_keys#show'
end
end
This is the output from rake routes:
GET /api/v2/special_keys(.:format) api/v2/special_keys#show {:format=>"json"}
And my spec:
require 'rails_helper'
describe Api::V2::SpecialKeysController do
describe 'GET #show' do
it 'gets the policy and signature' do
get '/api/v2/special_keys'
expect(response.status).to eql 200
end
end
end
Try to rewrite your test as:
require 'rails_helper'
describe Api::V2::SpecialKeysController do
describe 'GET #show' do
it 'gets the policy and signature' do
get '/api/v2/special_keys', {format: :json}
expect(response.status).to eql 200
end
end
end
Try:
resource :special_keys, only: [:show]
The singular tells the app, that there is only one. So it will only generate a show action that needs no id and no indexaction at all.

Rspec testing API versioning

I'm working with a web api in rails 4.1.8 and i'm doing some testing but always is returning me
Failure/Error: post '/v1/users', subdomain: 'api', user: #user.to_json, format: :json
ActionController::UrlGenerationError:
No route matches {:action=>"/v1/users", :controller=>"api/v1/users", :format=>:json, :subdomain=>"api", :user=>"JSON_OBJ"}
but if i test it in the browser with Advance Rest Console it works, i don't know what i'm doing wrong
here is me code
routes.rb
namespace :api, path: '/', constraints: {subdomain: 'api'}, defaults: { format: :json } do
namespace :v1 do
with_options except: [:edit, :new] do |except|
except.resources :users do
collection do
post 'login'
post 'showme'
end
end
except.resources :products
except.resources :locations
end
end
end
and my controller spec
module API
module V1
describe UsersController do
before do
request.host = "api.example.com"
expect({:post => "http://#{request.host}/v1/users"}).to(
route_to( controller: "api/v1/users",
action: "create",
subdomain: 'api',
format: :json
)
) # => PASS
# token expectations
#auth_token = allow(JWT::AuthToken).to(
receive(:make_token).and_return("mysecretkey")
)
expect(JWT::AuthToken.make_token({}, 3600)).to eq("mysecretkey")
end
describe "Create User" do
before(:each) do
#user = FactoryGirl.attributes_for :user
end
it 'should return a token' do
post '/v1/users', subdomain: 'api', user: #user.to_json, format: :json # Error
response_body = JSON.parse(response.body, symbolize_names: true)
expect(response_body['token']).to eql "mysecretkey"
end
end
end
end
end
rake routes
login_api_v1_users POST /v1/users/login(.:format) api/v1/users#login {:format=>:json, :subdomain=>"api"}
showme_api_v1_users POST /v1/users/showme(.:format) api/v1/users#showme {:format=>:json, :subdomain=>"api"}
api_v1_users GET /v1/users(.:format) api/v1/users#index {:format=>:json, :subdomain=>"api"}
POST /v1/users(.:format) api/v1/users#create {:format=>:json, :subdomain=>"api"}
api_v1_user GET /v1/users/:id(.:format) api/v1/users#show {:format=>:json, :subdomain=>"api"}
PATCH /v1/users/:id(.:format) api/v1/users#update {:format=>:json, :subdomain=>"api"}
PUT /v1/users/:id(.:format) api/v1/users#update {:format=>:json, :subdomain=>"api"}
DELETE /v1/users/:id(.:format) api/v1/users#destroy {:format=>:json, :subdomain=>"api"}
As described in http://api.rubyonrails.org/classes/ActionController/TestCase/Behavior.html which is referenced in https://www.relishapp.com/rspec/rspec-rails/docs/controller-specs, the first argument to post in your RSpec example is the name of the controller method to be called (i.e. :create in your case), not the route to that method.

Specs failing for api with nested routes

I'm working on adding an api to a ruby on rails todo list project as a learning exercise and I'm having problems testing some specs that use nested routes.
I'd like to show a list of uncompleted tasks when the /lists/1/ endpoint is used, but when I run my specs, there are UrlGenerationErrors and no route matches.
Any thoughts about how I can fix this? Is it a mistake in routes.rb?
FYI, I do have index responding to /user/1/lists and /lists/ so I can also show what lists are owned by a user and what public lists exist across the app. Is this what's confusing the app?
Thanks!
Specs
describe "#show" do
...
context "authorized user is the owner of the list" do
it "returns all uncompleted items" do
params = {list_id: #personal_list.id}
get :show, params
expect(response.status).to eq(200)
#expect(json).to json
end
end
context "authorized user when looking at a nonprivate list" do
it "returns all uncompleted items" do
params = {list_id: #open_list.id}
get :show, params
expect(response.status).to eq(200)
#expect(json).to json
end
end
context "authorized user when looking at a private list" do
it "returns error" do
authWithToken(#api.access_token)
params = {list_id: #private_list.id}
get :show, params
expect(response.status).to eq(400)
end
end
end
Failure Messages
:V1::ListsController#show authorized user is the owner of the list returns all uncompleted items
Failure/Error: get :show, params
ActionController::UrlGenerationError:
No route matches {:list_id=>"1", :controller=>"api/v1/lists", :action=>"show"}
# ./spec/controllers/api/v1/lists_controller_spec.rb:109:in `block (4 levels) in <top (required)>'
2) Api::V1::ListsController#show authorized user when looking at a nonprivate list returns all uncompleted items
Failure/Error: get :show, params
ActionController::UrlGenerationError:
No route matches {:list_id=>"2", :controller=>"api/v1/lists", :action=>"show"}
# ./spec/controllers/api/v1/lists_controller_spec.rb:126:in `block (4 levels) in <top (required)>'
3) Api::V1::ListsController#show authorized user when looking at a private list returns error
Failure/Error: get :show, params
ActionController::UrlGenerationError:
No route matches {:list_id=>"3", :controller=>"api/v1/lists", :action=>"show"}
# ./spec/controllers/api/v1/lists_controller_spec.rb:143:in `block (4 levels) in <top (required)>'
Routes
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :users, except: [:destroy, :new] do
resources :lists, only: [:index]
end
resources :lists, only: [:index, :show, :create, :update, :destroy] do
resources :items, only: [:create, :update, :destroy]
end
resources :items, only: [:destroy]
end
end
Prefix Verb URI Pattern Controller#Action
api_v1_user_lists GET /api/v1/users/:user_id/lists(.:format) api/v1/lists#index {:format=>"json"}
api_v1_users GET /api/v1/users(.:format) api/v1/users#index {:format=>"json"}
POST /api/v1/users(.:format) api/v1/users#create {:format=>"json"}
edit_api_v1_user GET /api/v1/users/:id/edit(.:format) api/v1/users#edit {:format=>"json"}
api_v1_user GET /api/v1/users/:id(.:format) api/v1/users#show {:format=>"json"}
PATCH /api/v1/users/:id(.:format) api/v1/users#update {:format=>"json"}
PUT /api/v1/users/:id(.:format) api/v1/users#update {:format=>"json"}
api_v1_list_items POST /api/v1/lists/:list_id/items(.:format) api/v1/items#create {:format=>"json"}
api_v1_list_item PATCH /api/v1/lists/:list_id/items/:id(.:format) api/v1/items#update {:format=>"json"}
PUT /api/v1/lists/:list_id/items/:id(.:format) api/v1/items#update {:format=>"json"}
DELETE /api/v1/lists/:list_id/items/:id(.:format) api/v1/items#destroy {:format=>"json"}
api_v1_lists GET /api/v1/lists(.:format) api/v1/lists#index {:format=>"json"}
POST /api/v1/lists(.:format) api/v1/lists#create {:format=>"json"}
api_v1_list GET /api/v1/lists/:id(.:format) api/v1/lists#show {:format=>"json"}
PATCH /api/v1/lists/:id(.:format) api/v1/lists#update {:format=>"json"}
PUT /api/v1/lists/:id(.:format) api/v1/lists#update {:format=>"json"}
DELETE /api/v1/lists/:id(.:format) api/v1/lists#destroy {:format=>"json"}
api_v1_item DELETE /api/v1/items/:id(.:format) api/v1/items#destroy {:format=>"json"}
Lists Controller for API
def show
#list = List.find(params[:id])
if (#list.permissions == "open") || (#list.user_id == #authorized_user)
render json: #list.items.completed, each_serializer: ItemSerializer
else
render json: #list.errors, status: :error
end
end
It looks like the problem is that you're sending the param of list_id instead of id.
Default rails routing for #show looks for an :id which is also what you've used (correctly) in your controller.
Try re-writing your tests as follows:
describe "#show" do
...
context "authorized user is the owner of the list" do
it "returns all uncompleted items" do
get :show, :id => #personal_list.id
end
OR
context "authorized user is the owner of the list" do
it "returns all uncompleted items" do
params = {id: #private_list.id}
get :show, params
end
...
end
The other thing to ensure is that you are using the correct top level description. Because this is a controller spec this needs to match the controller name. ie:
describe Api::V1::ListsController do
describe 'show' do

uninitialized constant SessionsController in API

I'm writing an API and am trying to login with the API and am getting uninitialized constant SessionsController error followed by the tip to "Try running rake routes for more information on available routes." when I try to go to the URL
http://localhost:3000/api/v1/login as a POST
My routes show that the route and action exist as shown:
api_v1_users GET /api/v1/users(.:format) api/v1/users#index {:format=>"json"}
POST /api/v1/users(.:format) api/v1/users#create {:format=>"json"}
new_api_v1_user GET /api/v1/users/new(.:format) api/v1/users#new {:format=>"json"}
edit_api_v1_user GET /api/v1/users/:id/edit(.:format) api/v1/users#edit {:format=>"json"}
api_v1_user GET /api/v1/users/:id(.:format) api/v1/users#show {:format=>"json"}
PUT /api/v1/users/:id(.:format) api/v1/users#update {:format=>"json"}
DELETE /api/v1/users/:id(.:format) api/v1/users#destroy {:format=>"json"}
new_api_v1_user_session GET /api/v1/login(.:format) sessions#new {:format=>"json"}
api_v1_user_session POST /api/v1/login(.:format) sessions#create {:format=>"json"}
destroy_api_v1_user_session DELETE /api/v1/logout(.:format) sessions#destroy {:format=>"json"}
api_v1_user_omniauth_authorize GET|POST /auth/:provider(.:format) authentications#passthru {:provider=>/twitter|facebook/, :format=>"json"}
api_v1_user_omniauth_callback GET|POST /auth/:action/callback(.:format) authentications#(?-mix:twitter|facebook) {:format=>"json"}
api_v1_user_password POST /api/v1/password(.:format) api/v1/passwords#create {:format=>"json"}
new_api_v1_user_password GET /api/v1/password/new(.:format) api/v1/passwords#new {:format=>"json"}
edit_api_v1_user_password GET /api/v1/password/edit(.:format) api/v1/passwords#edit {:format=>"json"}
PUT /api/v1/password(.:format) api/v1/passwords#update {:format=>"json"}
cancel_api_v1_user_registration GET /api/v1/cancel(.:format) registrations#cancel {:format=>"json"}
api_v1_user_registration POST /api/v1(.:format) registrations#create {:format=>"json"}
new_api_v1_user_registration GET /api/v1/sign_up(.:format) registrations#new {:format=>"json"}
edit_api_v1_user_registration GET /api/v1/edit(.:format) registrations#edit {:format=>"json"}
PUT /api/v1(.:format) registrations#update {:format=>"json"}
DELETE /api/v1(.:format) registrations#destroy {:format=>"json"}
When I try to access the account using the authentication_token url parameter, it works fine.
http://localhost:3000/api/v1/users/39?user_token=DxwspVzYSpsiLosd9xcE
Using that I can make PUT and GET requests no problem.
My routes look like this:
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :users
devise_for :users, :path => '', path_names: {sign_in: "login", sign_out: "logout"},
controllers: {omniauth_callbacks: "authentications", registrations: "registrations", sessions: "sessions"}
end
end
I assume it's a problem in my SessionsController, but can't figure out why the PUT request would work, but the POST wouldn't. Here is the SessionsController:
module Api
module V1
class SessionsController < Devise::SessionsController
before_filter :authenticate_user!, except: [:create, :destroy]
before_filter :ensure_params_exist
skip_before_filter :verify_authenticity_token
def create
resource = User.find_for_database_authentication(email: params[:user_login][:email])
return invalid_login_attempt unless resource
if resource.valid_password?(params[:user_login][:password])
sign_in("user", resource)
resource.ensure_authentication_token!
render 'api/v1/sessions/new.json.jbuilder', status: 201
return
end
invalid_login_attempt
end
def destroy
current_user.reset_authentication_token
render json: {success: true}
end
protected
def ensure_params_exist
return unless params[:user_login].blank?
render json: {success: false, message: "missing user_login parameter"}, status: 422
end
def invalid_login_attempt
render 'api/v1/sessions/invalid.json.jbuilder', status: 401
end
end
end
end
So in my routes file I had
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :users
devise_for :users, path: '', path_names: {sign_in: "login", sign_out: "logout"},
controllers: {omniauth_callbacks: "authentications", registrations: "registrations", sessions: "sessions"}
end
end
I removed sessions: "sessions" and everything works great. I'm not going to mark this as the correct answer because I don't know why this fixed the problem, or more specifically, why adding sessions: "sessions" was causing the error in the first place. Hopefully someone can explain this for future readers.
EDIT:
I realized much later that the problem was that I was referencing the sessions_controller, when I wanted to reference the api/v1/sessoins_controller There is no regular sessions_controller I just use the Devise one, but there is a sessions_controller in the API/V1 namespace. So that is why the problem was happening.
For future reference for anyone else who comes across this problem, the error is telling you the controller you're looking for doesn't exist as it should, so to troubleshoot, make sure you are looking for the controller in the correct place, and with the correct name.
In a similar case for an API app with rails 6 and devise 4.8.1 I used scope and devise_scopeto configure routes according to devises doc. devise_for was skip-ed.
In my case:
scope :api, defaults: { format: :json } do
scope :v1 do
devise_for :users, skip: :all
devise_scope :user do
post '/sign_in', to: 'devise/sessions#create'
delete '/sign_out', to: 'devise/sessions#destroy'
post '/sign_up', to: 'devise/registrations#create'
put '/account_update', to: 'devise/registrations#update'
delete '/account_delete', to: 'devise/registrations#destroy'
end
end
end

Resources