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.
Related
I have crafted some nested routes files using the below in my config/initializers:
class ActionDispatch::Routing::Mapper
def draw(routes_name, sub_path=nil)
if sub_path.present?
instance_eval(File.read(Rails.root.join("config/routes/#{sub_path}/#{routes_name}.rb")))
else
instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb")))
end
end
end
However, when used like this in the routes file:
Rails.application.routes.draw do
scope :api do
["v1"].map { |version| draw :base, "api/" + version }
end
end
The routes there do not appear nested as api/users etc. Not sure why the scope is getting ignored there.
EDIT: More Detailed Example
routes.rb
namespace :api, defaults: { format: :json } do
Rails.application.routes.draw do
["v1"].map { |version| draw :base, "api/" + version }
end
end
base.rb
namespace :v1 do
Rails.application.routes.draw do
[:identity].map { |path| draw path, "api/v1"}
end
end
identity.rb
Rails.application.routes.draw do
# Ensures proper namespace when fetching controllers, but does not add to path for routes
scope module: :identity do
namespace :users do
put '/', action: :update
patch '/', action: :update
get 'user_from_token', action: :update
end
end
end
Rails.application.routes.draw was redundant
# config/routes.rb
Rails.application.routes.draw do
scope :api do
["v1"].map { |version| draw :base, "api/" + version }
end
end
# config/routes/api/v1/base.rb
namespace :v1 do
# Rails.application.routes.draw do
[:identity].map { |path| draw path, "api/v1"}
# end
end
# config/routes/api/v1/identity.rb
# Rails.application.routes.draw do
# Ensures proper namespace when fetching controllers, but does not add to path for routes
scope module: :identity do
namespace :users do
put '/', action: :update
patch '/', action: :update
get 'user_from_token', action: :update
end
end
# end
$ rake routes
Prefix Verb URI Pattern Controller#Action
v1_users PUT /api/v1/users(.:format) v1/identity/users#update
PATCH /api/v1/users(.:format) v1/identity/users#update
v1_users_user_from_token GET /api/v1/users/user_from_token(.:format) v1/identity/users#update
rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
I configured json-resouce-api.
But the self link generated by json-resource-api is wrong.
The code seems to checks the module hierarchy of the resource class and completely ignores how rails generates the routes.
routes.rb
require 'api_constraints'
Rails.application.routes.draw do
namespace :api, defaults: { format: :json }, constraints: { subdomain: 'api' }, path: '/' do
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
# resources :subscriptions, only: [:index, :new, :create]
# jsonapi_resources :subscriptions, only: [:index, :new, :create]
jsonapi_resources :subscriptions
end
end
resouces/api/V1/subscription_recource.rb
class Api::V1::SubscriptionResource < JSONAPI::Resource
attributes :id, :third-service_id, :created_at, :updated_at
model_name 'Subscription'
# def custom_links(options)
# {self: nil}
# end
end
What I got => http://api.localhost.local:3000/api/v1/subscriptions/1
but it should be http://api.localhost.local:3000/subscriptions/1
How can I fix this?
UPDATE
rake routes
[DUPLICATE ATTRIBUTE] `id` has already been defined in SubscriptionResource.
Prefix Verb URI Pattern Controller#Action
api_v1_subscriptions GET /subscriptions(.:format) api/v1/subscriptions#index {:format=>:json, :subdomain=>"api"}
POST /subscriptions(.:format) api/v1/subscriptions#create {:format=>:json, :subdomain=>"api"}
api_v1_subscription GET /subscriptions/:id(.:format) api/v1/subscriptions#show {:format=>:json, :subdomain=>"api"}
PATCH /subscriptions/:id(.:format) api/v1/subscriptions#update {:format=>:json, :subdomain=>"api"}
PUT /subscriptions/:id(.:format) api/v1/subscriptions#update {:format=>:json, :subdomain=>"api"}
DELETE /subscriptions/:id(.:format) api/v1/subscriptions#destroy {:format=>:json, :subdomain=>"api"}
stripe_event /stripe-events StripeEvent::Engine
UPDATE2
This issue is totally the same as github.com/cerebris/jsonapi-resources/issues/591
Monkey Pack can be applied, but it's little bit risky.
For now ( 2016, Oct 5th ), I couldn't find any other ways than
namespace :api do
namespace :v1 do
jsonapi_resources :subscriptions
end
end
You can try it:
namespace :api, defaults: { format: :json }, constraints: { subdomain: 'api' }, path: "" do
namespace :v1, path: "" do
jsonapi_resources :subscriptions
end
end
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.
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
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