0

I am testing a series of API calls and am getting an error indicating various route verbs do not exist in the application, despite them being present.

So far as I can see the destroy method is there and allowed. The issue is the same with PATCH but I've just limited this to DELETE as I expect the root cause is probably the same.

What am I missing that I need in order to correct this?

Error:

6) /api/v1/users DELETE /destroy destroys the requested api/v1_user
     Failure/Error: delete api_v1_users_url(user), headers: valid_headers, as: :json

     ActionController::RoutingError:
       No route matches [DELETE] "/api/v1/users.5e9047c21d41c8454835b38b"
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/debug_exceptions.rb:36:in `call'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/railties-6.0.2.2/lib/rails/rack/logger.rb:38:in `call_app'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/railties-6.0.2.2/lib/rails/rack/logger.rb:26:in `block in call'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/activesupport-6.0.2.2/lib/active_support/tagged_logging.rb:80:in `block in tagged'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/activesupport-6.0.2.2/lib/active_support/tagged_logging.rb:28:in `tagged'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/activesupport-6.0.2.2/lib/active_support/tagged_logging.rb:80:in `tagged'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/railties-6.0.2.2/lib/rails/rack/logger.rb:26:in `call'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/request_id.rb:27:in `call'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/rack-2.2.2/lib/rack/runtime.rb:22:in `call'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/activesupport-6.0.2.2/lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/executor.rb:14:in `call'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/static.rb:126:in `call'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/rack-2.2.2/lib/rack/sendfile.rb:110:in `call'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/host_authorization.rb:77:in `call'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/railties-6.0.2.2/lib/rails/engine.rb:526:in `call'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/rack-test-1.1.0/lib/rack/mock_session.rb:29:in `request'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/rack-test-1.1.0/lib/rack/test.rb:266:in `process_request'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/rack-test-1.1.0/lib/rack/test.rb:119:in `request'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/actionpack-6.0.2.2/lib/action_dispatch/testing/integration.rb:270:in `process'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/actionpack-6.0.2.2/lib/action_dispatch/testing/integration.rb:42:in `delete'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/actionpack-6.0.2.2/lib/action_dispatch/testing/integration.rb:357:in `block (2 levels) in <module:Runner>'
     # ./spec/requests/api/v1/users_spec.rb:101:in `block (4 levels) in <top (required)>'
     # ./spec/requests/api/v1/users_spec.rb:100:in `block (3 levels) in <top (required)>'
     # ./spec/rails_helper.rb:17:in `block (3 levels) in <top (required)>'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/database_cleaner-1.8.4/lib/database_cleaner/generic/base.rb:16:in `cleaning'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/database_cleaner-1.8.4/lib/database_cleaner/configuration.rb:87:in `block (2 levels) in cleaning'
     # /home/etherk1ll/.rvm/gems/ruby-2.7.0/gems/database_cleaner-1.8.4/lib/database_cleaner/configuration.rb:88:in `cleaning'
     # ./spec/rails_helper.rb:16:in `block (2 levels) in <top (required)>'

Finished in 1.33 seconds (files took 3.57 seconds to load)
16 examples, 6 failures

Failed examples:

rspec ./spec/requests/api/v1/users_spec.rb:47 # /api/v1/users POST /create with invalid parameters does not create a new user
rspec ./spec/requests/api/v1/users_spec.rb:54 # /api/v1/users POST /create with invalid parameters renders a JSON response with errors for the new api/v1_user
rspec ./spec/requests/api/v1/users_spec.rb:69 # /api/v1/users PATCH /update with valid parameters updates the requested api/v1_user
rspec ./spec/requests/api/v1/users_spec.rb:77 # /api/v1/users PATCH /update with valid parameters renders a JSON response with the api/v1_user
rspec ./spec/requests/api/v1/users_spec.rb:87 # /api/v1/users PATCH /update with invalid parameters renders a JSON response with errors for the api/v1_user
rspec ./spec/requests/api/v1/users_spec.rb:98 # /api/v1/users DELETE /destroy destroys the requested api/v1_user

Test

describe "DELETE /destroy" do
    it "destroys the requested api/v1_user" do
      user = Api::V1::User.create! valid_attributes
      expect {
        delete api_v1_users_url(user), headers: valid_headers, as: :json
      }.to change(Api::V1::User, :count).by(-1)
    end
  end

users_controller.rb

class Api::V1::UsersController < ApplicationController
  before_action :set_user, only: [:show, :update, :destroy]

  # GET /api/v1/users
  def index
    @users = Api::V1::User.all

    render json: @users
  end

  # GET /api/v1/users/1
  def show
    render json: @user
  end

  # POST /api/v1/users
  def create
    @user = Api::V1::User.new(user_params)

    if @user.save
      render json: @user, status: :created, location: @user
    else
      render json: @user.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /api/v1/users/1
  def update
    if @user.update(user_params)
      render json: @user
    else
      render json: @user.errors, status: :unprocessable_entity
    end
  end

  # DELETE /api/v1/users/1
  def destroy
    @user.destroy
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = Api::V1::User.find(params[:id])
    end

    # Only allow a trusted parameter "white list" through.
    def user_params
      params.require(:user).permit(:first_name, :second_name, :username, :email, :password)
    end
end

routes.rb

Rails.application.routes.draw do

  namespace :api do
    namespace :v1 do
      resources :users
    end
  end
end
3therk1ll
  • 2,056
  • 4
  • 35
  • 64
  • If you look at that error message your spec is requesting ``"/api/v1/users.5e9047c21d41c8454835b38b" not `"/api/v1/users/5e9047c21d41c8454835b38b"`. I'm guessing your strangly non-conventional `api_v1_users_url` helper call is to blame. – max Apr 10 '20 at 10:40
  • There are some other things that are really off here as well. Explicitly nest your classes in modules. Do not use the scope resolution operator (`::`) when defining classes. The results can be extremely unexpected as the module nesting inside the class is all wrong. Also versioning your models does not really make sense. See https://stackoverflow.com/questions/34075588/rest-api-versioning-why-arent-models-versioned/34076175#34076175 – max Apr 10 '20 at 10:43
  • I think u shouldn't use plural users in api_v1_users_path. Also, running rake routes is an easy way to say the named helpers and will help u debug this. – Joel Blum Apr 10 '20 at 10:47

2 Answers2

0

For those struggling with any similar issue. The fault with this lay with my nested /api/v1 folder structure which as suggested by Max above caused the somewhat ugly api_v1_users_url(user). This originated through my using scaffolding rather than adding the files manually.

It seems rails uses the full path to the controller to construct objects and associated variables.

I refactored all the class declarations and rspec file paths as below.

This appears to have resolved the initial code smells mentioned in teh comments as well as the initial issue.

➜  api git:(authentication) ✗ tree spec 
spec
├── factories
│   └── users.rb
├── models
├── rails_helper.rb
├── requests
│   └── api
│       └── users_spec.rb
├── routing
│   └── users_routing_spec.rb
├── spec_helper.rb
└── support
    └── factory_bot.rb



➜  api git:(authentication) ✗ tree app 
app
├── channels
│   └── application_cable
│       ├── channel.rb
│       └── connection.rb
├── controllers
│   ├── application_controller.rb
│   ├── concerns
│   └── users_controller.rb
├── jobs
│   └── application_job.rb
├── mailers
│   └── application_mailer.rb
├── models
│   ├── concerns
│   └── user.rb
└── views
    └── layouts
        ├── mailer.html.erb
        └── mailer.text.erb

The corrected Rspec tests generally now look like this:

  describe "DELETE /destroy" do
    it "destroys the requested user" do
      user = User.create! valid_attributes
      expect {
        delete user_url(user),headers: valid_headers, as: :json
      }.to change(User, :count).by(-1)
    end
  end

Removing the nested declarations:

user = Api::V1::User.create! valid_attributes
3therk1ll
  • 2,056
  • 4
  • 35
  • 64
-1

Maybe try

api_v1_user_path(user)
Joel Blum
  • 7,750
  • 10
  • 41
  • 60