My routes.rb is :
Rails.application.routes.draw do
namespace :api, path: "/", constraint: { subdomain: "api" }, defaults: { format: :json } do
scope module: :v1 do
resources :users, only: [:show]
end
end
end
My users_controller.rb is :
class Api::V1::UsersController < ApplicationController
respond_to :json
def show
respond_with User.find(params[:id])
end
end
My test is :
require 'test_helper'
class Api::V1::UsersControllerTest < ActionController::TestCase
test '#show displays the specific user' do
#user = FactoryGirl.create(:user)
get :show, id: #user.id, format: :json
assert_response :success
end
end
Why does this test give the following error:
ActionController::UrlGenerationError: No route matches {:action=>"show", :controller=>"api/v1/users", :format=>:json, :id=>1}
I just needed to add the constraint to the test:
get :show, id: #user.id, format: :json, :constraint=>{:subdomain=>"api"}
Related
I am building an API and, upon writing the tests, I run into a strange UrlGenerator Error.
I have an API on version one and this is my Users controller.
class Api::V1::UsersController < ApplicationController
respond_to :json
def show
respond_with User.find(params[:id])
end
end
Here is the spec for that users controller
require 'rails_helper'
RSpec.describe Api::V1::UsersController, type: :controller do
before(:each) { request.headers['Accept'] = "application/vnd.marketplace.v1" }
describe "GET #show" do
before(:each) do
#user = FactoryBot.create :user
get :show, format: :json
end
it "returns the information about a reporter on a hash" do
user_response = JSON.parse(response.body, symbolize_names: true)
expect(user_response[:email]).to eql #user.email
end
it { should respond_with 200 }
end
end
When I run this spec I get the following error message: `Failure/Error: get :show, format: :json
ActionController::UrlGenerationError:
No route matches {:action=>"show", :controller=>"api/v1/users", :format=>:json}`
I have only one route for my API:
api_user GET /users/:id(.:format) api/v1/users#show {:subdomain=>"api", :format=>:json}
Does anybody know why I would be getting this error? It seems to me that, based on the route returned from the api routes list, this should be working. My routes.rb file is listed below:
namespace :api, defaults: { format: :json }, constraints: { subdomain: 'api' }, path: '/' do
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
resources :users, :only => [:show]
end
end
The problem is that the show route that you have defined requires an :id parameter, but the call for get :show from the test does not send it.
From Rspec, you can send the id with something like:
get :show, params: { id: #user.id }, format: :json
I'm trying to remove the obsolete routes of devise, in my api_only rails setup. However i'm in a fuss about how to define them properly with devise_scope. I have the following routes.rb:
# config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :users do
devise_scope :user do
resource :confirmations, only: %i[create show], format: false
end
end
end
end
Which refers to the confirmations_controller that contains custom json renders instead of the typical respond_with:
# app/controllers/api/users/confirmations_controller.rb
module Api
module Users
class ConfirmationsController < Devise::ConfirmationsController
# POST /resource/confirmation
def create
self.resource = resource_class.send_confirmation_instructions(resource_params)
yield resource if block_given?
if successfully_sent?(resource)
# respond_with({}, location: after_resending_confirmation_instructions_path_for(resource_name))
render json: { status: 201 }, status: :created
else
# respond_with(resource)
render json: { status: 422, errors: resource.errors.keys },
status: :unprocessable_entity
end
end
# GET /resource/confirmation?confirmation_token=abcdef
def show
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
yield resource if block_given?
if resource.errors.empty?
set_flash_message!(:notice, :confirmed)
# respond_with_navigational(resource) { redirect_to after_confirmation_path_for(resource_name, resource) }
render json: { status: 200 }, status: :ok
else
# respond_with_navigational(resource.errors, status: :unprocessable_entity) { render :new }
render json: { status: 422, errors: resource.errors.keys },
status: :unprocessable_entity
end
end
end
end
end
As can be seen in the routes.rb I only need the create and show endpoints of confirmations. However the current definition results in the following error when running rspec:
Failure/Error: get api_users_confirmations_path, params: { confirmation_token: 'incorrect_token' }
AbstractController::ActionNotFound:
Could not find devise mapping for path "/api/users/confirmations?confirmation_token=incorrect_token".
This may happen for two reasons:
1) You forgot to wrap your route inside the scope block. For example:
devise_scope :user do
get "/some/route" => "some_devise_controller"
end
2) You are testing a Devise controller bypassing the router.
If so, you can explicitly tell Devise which mapping to use:
#request.env["devise.mapping"] = Devise.mappings[:user]
Which tends mostly to the missing devise mapping, considering that the devise_scope is defined properly. However i'm not sure how to solve this properly without having to include the bindings in every devise controller. Is this doable from the routes?
I have never tried to use resources inside of devise_scope.
This is how I have defined it.
devise_scope :user do
delete 'logout', to: 'devise/sessions#destroy'
end
This is how I have defined in one of my application
devise_for :users, path: 'api/v1/accounts', controllers: {
:registrations => 'api/v1/accounts/registrations',
:sessions => 'api/v1/accounts/sessions',
:passwords => 'api/v1/accounts/passwords'
}
devise_scope :user do
get '/sessions/new' => "sessions#new", :as => :new_sessions
get '/sessions/forgot_password' => "sessions#forgot_password", :as => :forgot_password
post '/validate_referral_code' => 'validates#validate_referral_code', as: :validate_referral_code
post '/validate_employment_code' => 'validates#validate_employment_code', as: :validate_employment_code
post '/get_weather' => 'temperature#weather', as: :weather
get '/fetch' => 'zip_codes#fetch', as: :fetch_zip_code
post '/request_demo' => 'demos#create', as: :create
end
namespace :api do
namespace :v1 do
scope :accounts do
resources :third_party_logins, only: [] do
collection do
get :action_name
end
end
end
end
end
I am learning api on rails,and my code is below
routes.rb
require 'api_constraints'
Rails.application.routes.draw do
devise_for :users
namespace :api, defaults: { format: :json }, constraints: { subdomain: 'api' }, path: '/' do
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
resources :users, :only => [:show]
end
end
end
users_controllers.rb
class Api::V1::UsersController < ApplicationController
respond_to :json
def show
respond_with User.find(params[:id])
end
end
when I run the rails server with localhost:3000/users/1 then It gives me the error No route matches
Then i checked routes using rake routes and it is included in my routes
`api_user GET /users/:id(.:format) api/v1/users#show'
but i don't know why it gives me the error
api_constraints.rb
class ApiConstraints
def initialize(options)
#version = options[:version]
#default = options[:default]
end
def matches?(req)
#default || req.headers['Accept'].include?("application/vnd.marketplace.v#{#version}")
end
end
`
Try the following:
api.lvh.me:3000/api/v1/users/1
You've setup a constraint that requires an api sub domain. You'll need to remove the constraint to make the following work:
lvh.me:3000/api/v1/users/1
Note: lvh.me points to 127.0.0.1
When running my controller test, I get this error:
NoMethodError:
undefined method `api_challenge_url' for #<Api::V1::ChallengesController:0x007f829b233460>
Which, is in fact, not a route that exists. My routes file looks like this:
namespace :api, { format: :json, constraints: { subdomain: 'api' }, path: '/'} do
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
resources :users, only: [:show, :create, :update, :destroy] do
resources :challenges, only: [:create, :show]
end
end
end
And my controller test looks like this:
RSpec.describe Api::V1::ChallengesController, type: :controller do
describe "POST #create" do
context "when successfully created" do
before(:each) do
#user = FactoryGirl.create(:user)
#challenge_attributes = FactoryGirl.attributes_for(:challenge)
post :create, user_id: #user.id, challenge: #challenge_attributes, format: :json
end
it "should render the JSON for the created challenge" do
challenge_response = JSON.parse(response.body, symbolize_names: true)
expect challenge_response[:description].to eql #challenge_attributes["description"]
expect challenge_response[:title].to eql #challenge_attributes["title"]
end
end
end
end
But for the life of me, I can't why it's calling the wrong route name. The output of the relevant part of the rake routes looks like this:
api_user_challenges POST /users/:user_id/challenges(.:format) api/v1/challenges#create {:subdomain=>"api"}
I've tried a few different formats in the post method, is there some idiomatic way of doing this that I'm missing?
Try adding some configuration to include url helpers into your test suite:
RSpec.configure do |c|
c.include Rails.application.routes.url_helpers
# Other configurations ...
end
And if you prefer using xxx_url over xxx_path, remember to config action_controller.default_url_options in your config/environments/test.rb, for example:
config.action_controller.default_url_options = {
host: 'www.mysite.org',
protocol: 'https'
}
I have the following resource set up in my routes.rb file:
namespace :api, path: '/', defaults: { format: 'json' }, constraints: { subdomain: 'api' } do
namespace :v1 do
resources :yums do
resources :likes, only: [:create, :destroy, :index] do
match '/', to: 'likes#destroy', via: 'delete'
end
end
end
end
I also wrote a spec to test my requests:
describe "Like API requests" do
before (:each) do
host! 'api.example.com'
#user = FactoryGirl.create(:user)
#other_user = FactoryGirl.create(:user)
#yum = FactoryGirl.create(:yum, user: #other_user)
end
describe "liking a Yum" do
it "should increase the Yum's Like count" do
expect do
post "/v1/yums/#{#yum.id}/likes", { authorization: token_header(#user.auth_token) }
end.to change(#yum.reload.likes, :count).by(1)
end
end
describe "unliking a Yum" do
it "should increase the Yum's Like count" do
expect do
delete "/v1/yums/#{#yum.id}/likes", { authorization: token_header(#user.auth_token) }
end.to change(#yum.reload.likes, :count).by(-1)
end
end
end
I want the Likes controller to not require an ID for the destroy action since I use methods on my user model to like and unlike, but this scheme doesn't seem to be working, what am I doing wrong?
Try to add a route in your routes.rb like :
match "v1/yums/:yum_id/likes", to: "likes#destroy", via: "delete", defaults: { id: nil }