7

This is my controller:

class Api::V1::UsersController < ApplicationController
  respond_to :json

  def show
    respond_with User.find(params[:id])
  end
end

This is my routes.rb

require 'api_constraints'

Rails.application.routes.draw do
  devise_for :users
  # Api definition
  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

This is my lib/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.myapp.v#{@version}")
  end
end

I added a record to my DB as can be seen here:

[3] pry(main)> User.all
  User Load (0.4ms)  SELECT "users".* FROM "users"
=> [#<User:0x007fbdb31079f8
  id: 1,
  email: "abc@test.com",
  encrypted_password: "$2a$11$rvOrK1bmuuNwwc78ERxG3eCrKiUu9NTZsJ/nmirqb.3yRBHYUK69S",
  reset_password_token: nil,
  reset_password_sent_at: nil,
  remember_created_at: nil,
  sign_in_count: 0,
  current_sign_in_at: nil,
  last_sign_in_at: nil,
  current_sign_in_ip: nil,
  last_sign_in_ip: nil,
  created_at: Mon, 11 Apr 2016 10:35:39 UTC +00:00,
  updated_at: Mon, 11 Apr 2016 10:35:39 UTC +00:00>]

Yet this is the error I get in my rails console:

ActionController::RoutingError (No route matches [GET] "/users/1"):

actionpack (5.0.0.beta3) lib/action_dispatch/middleware/debug_exceptions.rb:53:in `call'
web-console (3.1.1) lib/web_console/middleware.rb:131:in `call_app'
web-console (3.1.1) lib/web_console/middleware.rb:28:in `block in call'
web-console (3.1.1) lib/web_console/middleware.rb:18:in `catch'
web-console (3.1.1) lib/web_console/middleware.rb:18:in `call'
actionpack (5.0.0.beta3) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'
railties (5.0.0.beta3) lib/rails/rack/logger.rb:36:in `call_app'
railties (5.0.0.beta3) lib/rails/rack/logger.rb:24:in `block in call'
activesupport (5.0.0.beta3) lib/active_support/tagged_logging.rb:70:in `block in tagged'
activesupport (5.0.0.beta3) lib/active_support/tagged_logging.rb:26:in `tagged'
activesupport (5.0.0.beta3) lib/active_support/tagged_logging.rb:70:in `tagged'
railties (5.0.0.beta3) lib/rails/rack/logger.rb:24:in `call'
actionpack (5.0.0.beta3) lib/action_dispatch/middleware/request_id.rb:24:in `call'
rack (2.0.0.alpha) lib/rack/runtime.rb:22:in `call'
activesupport (5.0.0.beta3) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'
actionpack (5.0.0.beta3) lib/action_dispatch/middleware/load_interlock.rb:13:in `call'
actionpack (5.0.0.beta3) lib/action_dispatch/middleware/static.rb:136:in `call'
rack (2.0.0.alpha) lib/rack/sendfile.rb:111:in `call'
railties (5.0.0.beta3) lib/rails/engine.rb:522:in `call'
puma (3.2.0) lib/puma/configuration.rb:227:in `call'
puma (3.2.0) lib/puma/server.rb:561:in `handle_request'
puma (3.2.0) lib/puma/server.rb:406:in `process_client'
puma (3.2.0) lib/puma/server.rb:271:in `block in run'
puma (3.2.0) lib/puma/thread_pool.rb:111:in `block in spawn_thread'

And this is what I see in my browser:

{"status":404,"error":"Not Found","exception":"#\u003cActionController::RoutingError: No route matches [GET] \"/users/1\"\u003e","traces":{"Application Trace":[],"Framework Trace":[{"id":0,"trace":"actionpack (5.0.0.beta3) lib/action_dispatch/middleware/debug_exceptions.rb:53:in `call'"},{"id":1,"trace":"web-console (3.1.1) lib/web_console/middleware.rb:131:in `call_app'"},{"id":2,"trace":"web-console (3.1.1) lib/web_console/middleware.rb:28:in `block in call'"},{"id":3,"trace":"web-console (3.1.1) lib/web_console/middleware.rb:18:in `catch'"},{"id":4,"trace":"web-console (3.1.1) lib/web_console/middleware.rb:18:in `call'"},{"id":5,"trace":"actionpack (5.0.0.beta3) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'"},{"id":6,"trace":"railties (5.0.0.beta3) lib/rails/rack/logger.rb:36:in `call_app'"},{"id":7,"trace":"railties (5.0.0.beta3) lib/rails/rack/logger.rb:24:in `block in call'"},{"id":8,"trace":"activesupport (5.0.0.beta3) lib/active_support/tagged_logging.rb:70:in `block in tagged'"},{"id":9,"trace":"activesupport (5.0.0.beta3) lib/active_support/tagged_logging.rb:26:in `tagged'"},{"id":10,"trace":"activesupport (5.0.0.beta3) lib/active_support/tagged_logging.rb:70:in `tagged'"},{"id":11,"trace":"railties (5.0.0.beta3) lib/rails/rack/logger.rb:24:in `call'"},{"id":12,"trace":"actionpack (5.0.0.beta3) lib/action_dispatch/middleware/request_id.rb:24:in `call'"},{"id":13,"trace":"rack (2.0.0.alpha) lib/rack/runtime.rb:22:in `call'"},{"id":14,"trace":"activesupport (5.0.0.beta3) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'"},{"id":15,"trace":"actionpack (5.0.0.beta3) lib/action_dispatch/middleware/load_interlock.rb:13:in `call'"},{"id":16,"trace":"actionpack (5.0.0.beta3) lib/action_dispatch/middleware/static.rb:136:in `call'"},{"id":17,"trace":"rack (2.0.0.alpha) lib/rack/sendfile.rb:111:in `call'"},{"id":18,"trace":"railties (5.0.0.beta3) lib/rails/engine.rb:522:in `call'"},{"id":19,"trace":"puma (3.2.0) lib/puma/configuration.rb:227:in `call'"},{"id":20,"trace":"puma (3.2.0) lib/puma/server.rb:561:in `handle_request'"},{"id":21,"trace":"puma (3.2.0) lib/puma/server.rb:406:in `process_client'"},{"id":22,"trace":"puma (3.2.0) lib/puma/server.rb:271:in `block in run'"},{"id":23,"trace":"puma (3.2.0) lib/puma/thread_pool.rb:111:in `block in spawn_thread'"}],"Full Trace":[{"id":0,"trace":"actionpack (5.0.0.beta3) lib/action_dispatch/middleware/debug_exceptions.rb:53:in `call'"},{"id":1,"trace":"web-console (3.1.1) lib/web_console/middleware.rb:131:in `call_app'"},{"id":2,"trace":"web-console (3.1.1) lib/web_console/middleware.rb:28:in `block in call'"},{"id":3,"trace":"web-console (3.1.1) lib/web_console/middleware.rb:18:in `catch'"},{"id":4,"trace":"web-console (3.1.1) lib/web_console/middleware.rb:18:in `call'"},{"id":5,"trace":"actionpack (5.0.0.beta3) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'"},{"id":6,"trace":"railties (5.0.0.beta3) lib/rails/rack/logger.rb:36:in `call_app'"},{"id":7,"trace":"railties (5.0.0.beta3) lib/rails/rack/logger.rb:24:in `block in call'"},{"id":8,"trace":"activesupport (5.0.0.beta3) lib/active_support/tagged_logging.rb:70:in `block in tagged'"},{"id":9,"trace":"activesupport (5.0.0.beta3) lib/active_support/tagged_logging.rb:26:in `tagged'"},{"id":10,"trace":"activesupport (5.0.0.beta3) lib/active_support/tagged_logging.rb:70:in `tagged'"},{"id":11,"trace":"railties (5.0.0.beta3) lib/rails/rack/logger.rb:24:in `call'"},{"id":12,"trace":"actionpack (5.0.0.beta3) lib/action_dispatch/middleware/request_id.rb:24:in `call'"},{"id":13,"trace":"rack (2.0.0.alpha) lib/rack/runtime.rb:22:in `call'"},{"id":14,"trace":"activesupport (5.0.0.beta3) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'"},{"id":15,"trace":"actionpack (5.0.0.beta3) lib/action_dispatch/middleware/load_interlock.rb:13:in `call'"},{"id":16,"trace":"actionpack (5.0.0.beta3) lib/action_dispatch/middleware/static.rb:136:in `call'"},{"id":17,"trace":"rack (2.0.0.alpha) lib/rack/sendfile.rb:111:in `call'"},{"id":18,"trace":"railties (5.0.0.beta3) lib/rails/engine.rb:522:in `call'"},{"id":19,"trace":"puma (3.2.0) lib/puma/configuration.rb:227:in `call'"},{"id":20,"trace":"puma (3.2.0) lib/puma/server.rb:561:in `handle_request'"},{"id":21,"trace":"puma (3.2.0) lib/puma/server.rb:406:in `process_client'"},{"id":22,"trace":"puma (3.2.0) lib/puma/server.rb:271:in `block in run'"},{"id":23,"trace":"puma (3.2.0) lib/puma/thread_pool.rb:111:in `block in spawn_thread'"}]}}

Edit 1

This is my rake routes

$ rake routes
[DEPRECATION] `last_comment` is deprecated.  Please use `last_description` instead.
[DEPRECATION] `last_comment` is deprecated.  Please use `last_description` instead.
[DEPRECATION] `last_comment` is deprecated.  Please use `last_description` instead.
                  Prefix Verb   URI Pattern                    Controller#Action
        new_user_session GET    /users/sign_in(.:format)       devise/sessions#new
            user_session POST   /users/sign_in(.:format)       devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)      devise/sessions#destroy
           user_password POST   /users/password(.:format)      devise/passwords#create
       new_user_password GET    /users/password/new(.:format)  devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
                         PATCH  /users/password(.:format)      devise/passwords#update
                         PUT    /users/password(.:format)      devise/passwords#update
cancel_user_registration GET    /users/cancel(.:format)        devise/registrations#cancel
       user_registration POST   /users(.:format)               devise/registrations#create
   new_user_registration GET    /users/sign_up(.:format)       devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)          devise/registrations#edit
                         PATCH  /users(.:format)               devise/registrations#update
                         PUT    /users(.:format)               devise/registrations#update
                         DELETE /users(.:format)               devise/registrations#destroy
                api_user GET    /users/:id(.:format)           api/v1/users#show {:format=>:json, :subdomain=>"api"}

Edit 2

This is my application controller:

class ApplicationController < ActionController::API
end

Edit 3

This is my users_controller_spec.rb test that actually passes:

require 'rails_helper'

describe Api::V1::UsersController do
  before(:each) { request.headers['Accept'] = "application/vnd.myapp.v1" }

  describe "GET #show" do
    before(:each) do
      @user = FactoryGirl.create :user
      get :show, id: @user.id, format: :json
    end

    it "returns the information about a user 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

This is the result:

$ rspec spec/controllers
DEPRECATION WARNING: use_transactional_fixtures= is deprecated and will be removed from Rails 5.1 (use use_transactional_tests= instead). (called from <top (required)> at /myapp/spec/controllers/api/v1/users_controller_spec.rb:3)
DEPRECATION WARNING: ActionController::TestCase HTTP request methods will accept only
keyword arguments in future Rails versions.

Examples:

get :show, params: { id: 1 }, session: { user_id: 1 }
process :update, method: :post, params: { id: 1 }
 (called from block (3 levels) in <top (required)> at /myapp/spec/controllers/api/v1/users_controller_spec.rb:9)
DEPRECATION WARNING: ActionController::TestCase HTTP request methods will accept only
keyword arguments in future Rails versions.

Examples:

get :show, params: { id: 1 }, session: { user_id: 1 }
process :update, method: :post, params: { id: 1 }
 (called from block (3 levels) in <top (required)> at /myapp/spec/controllers/api/v1/users_controller_spec.rb:9)
.DEPRECATION WARNING: ActionController::TestCase HTTP request methods will accept only
keyword arguments in future Rails versions.

Examples:

get :show, params: { id: 1 }, session: { user_id: 1 }
process :update, method: :post, params: { id: 1 }
 (called from block (3 levels) in <top (required)> at /myapp/spec/controllers/api/v1/users_controller_spec.rb:9)
DEPRECATION WARNING: ActionController::TestCase HTTP request methods will accept only
keyword arguments in future Rails versions.

Examples:

get :show, params: { id: 1 }, session: { user_id: 1 }
process :update, method: :post, params: { id: 1 }
 (called from block (3 levels) in <top (required)> at /myapp/spec/controllers/api/v1/users_controller_spec.rb:9)
.

Finished in 0.41411 seconds (files took 3.33 seconds to load)
2 examples, 0 failures
marcamillion
  • 32,933
  • 55
  • 189
  • 380
  • Can you add the output of `rake routes` ?. Maybe there's a problem with the namespace api and the path '/' – coding addicted Apr 11 '16 at 11:40
  • @codingaddicted I added the output of that `rake routes`. Refresh the question. – marcamillion Apr 11 '16 at 11:42
  • Very strange. At first look I don't see where this can come from... Anything in your application controller that can interfere ? – coding addicted Apr 11 '16 at 11:54
  • @codingaddicted Nope. I added it for completeness sake, but there isn't anything there. – marcamillion Apr 11 '16 at 12:01
  • Also, I wrote some tests and they actually work. – marcamillion Apr 11 '16 at 12:04
  • When you said your accessing it through browser. I assume you did something like localhost:3000/users/1.json ? Did you try with another tool? Curl maybe – coding addicted Apr 11 '16 at 12:11
  • I am doing it at `localhost:3000/users/1`. I also tried `curl` and got the same error. – marcamillion Apr 11 '16 at 12:15
  • I'm out of idea for the moment. Sorry that I can't be more helpful. – coding addicted Apr 11 '16 at 12:22
  • Sall good. Glad to know I am not the only one....this has been driving me crazy. I wonder if there is some obscure Rails 5 beta bug that I don't know about :| – marcamillion Apr 11 '16 at 12:23
  • 1
    Yeah, could be Rails5 related or we're just missing something small. I'll follow your question till there's an answer! – coding addicted Apr 11 '16 at 12:24
  • Named route is api_user, not user... Could you try `respond with api_user_path User.find(params[:id])`? – Ruby Racer Apr 11 '16 at 13:52
  • @RubyRacer That doesn't help. Note that the `respond_with` is in the controller, so all it does is returns the DB object. The controller should know the path for that `controller#action`. – marcamillion Apr 11 '16 at 18:27
  • I know this is not rogular, that's why I'm throwing stones at it... If you go to console, what is the output of `app.url_for(User.first)`? – Ruby Racer Apr 11 '16 at 18:40
  • @RubyRacer Interesting, I've never used that before. This is what we get: `[1] pry(main)> app.url_for(User.first) User Load (3.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]] NoMethodError: undefined method 'user_url' for # from /.rvm/gems/ruby-2.3.0@myapp/gems/actionpack-5.0.0.beta3/lib/action_dispatch/testing/assertions/routing.rb:172:in 'method_missing'` – marcamillion Apr 12 '16 at 03:27

1 Answers1

5

The problem is that your route has a constraint requiring a subdomain of api. So http://api.lvh.me:3000/users/1.json will work, but not http://localhost:3000/users/1.json or http://lvh.me:3000/users/1.json.

You should change your Ajax call to use an api subdomain. You can also confirm this by writing a request test (not a controller test), and see that it gets a 200 when using an api subdomain, and a 404 otherwise.

Paul A Jungwirth
  • 23,504
  • 14
  • 74
  • 93
  • I had a suspicion that this might be it. How can I use a subdomain with localhost, or is there no way for me to do so? The approach they took in the book used pow or some other server, which I couldn't get working. So I am just using the vanilla Rails Server. – marcamillion Apr 14 '16 at 19:36
  • lvh.me resolves to 127.0.0.1, so you don't need to run anything special. Just say `lvh.me` instead of `localhost`. – Paul A Jungwirth Apr 14 '16 at 20:43
  • Ok when I go to `http://api.lvh.me/users/1` it says 404 not found. Also, where can I see the server logs for the requests that go to that subdomain? – marcamillion Apr 14 '16 at 20:49
  • Is your latest 404 still a routing error, or is there just no user with ID 1? The logs all go to the same place. – Paul A Jungwirth Apr 14 '16 at 20:56
  • That's the thing, I can't tell if it is a routing error, because my `rails s` doesn't show that log. There is a User with ID=1 (see my `rails c` above). Also, no matter what I go to in my app, it returns a 404. Not just `/users/1`. – marcamillion Apr 14 '16 at 21:10
  • It looks like you are leaving off the port number. Don't go to http://api.lvh.me/. Go to http://api.lvh.me:3000/. That's why nothing appears in your `rails s` output. – Paul A Jungwirth Apr 14 '16 at 21:19
  • Ok...when I do that, I see this in my browser: `This site can’t be reached api.lvh.me refused to connect. ERR_CONNECTION_REFUSED`. And I don't see anything in my `rails s` output. The request doesn't seem to reach the server. Keep in mind I added the port there. In fact, I get the same thing if I just click the link in your comment. – marcamillion Apr 14 '16 at 21:26
  • In that case I would run `getent hosts api.lvh.me` and check your `/etc/hosts` file to make sure the domain is resolving to 127.0.0.1. Also, can you connect to http://127.0.0.1:3000/ ? – Paul A Jungwirth Apr 14 '16 at 21:34
  • 127.0.0.1:3000 doesn't work either. I ran `getent hosts api.lvh.me` and got a bash command not found error. 127.0.0.1 with no port, sends me to the same Pow is installed page that I saw when I did `lvh.me` – marcamillion Apr 14 '16 at 21:54
  • "127.0.0.1:3000 doesn't work either": You're sure Rails is running? And it's listening on port 3000? What is the full `rails s` command you're using? Did you change the port Rails is listening on? – Paul A Jungwirth Apr 14 '16 at 23:06
  • I am very sure Rails is running. I am running Rails 5, API flag. I am started my puma server with `rails s`. When I go to localhost:3000, I see this in my logs: `Started GET "/" for ::1 at 2016-04-14 18:09:32 -0500 Processing by Rails::WelcomeController#index as HTML Parameters: {"internal"=>true} Rendered myapp/gems/railties-5.0.0.beta3/lib/rails/templates/rails/welcome/index.html.erb (4.8ms) Completed 200 OK in 16ms (Views: 9.9ms | ActiveRecord: 0.0ms)` I did not change the port Rails is listening on. For some reason, the 127 and lvh.me options don't hit the Rails server. – marcamillion Apr 14 '16 at 23:11
  • Oh you are using puma. . . . It is only binding to the ipv6 address, not the ipv4 one. Read [here](http://stackoverflow.com/questions/27768479/why-is-puma-only-binding-to-tcp6-via-rails-s) and [here](https://github.com/puma/puma/issues/782) and [here](https://github.com/rails/rails/issues/19815). – Paul A Jungwirth Apr 15 '16 at 00:07
  • Interesting...I normally use thin, but decided to use puma given that's the default web server that comes with Rails 5 beta and I am using the API flag. I switched to Thin and still getting a similar error, so it's not unique to Puma at all. – marcamillion Apr 15 '16 at 13:21
  • Any further ideas here? – marcamillion Apr 18 '16 at 19:07
  • I think the Puma issue is pretty clearly separate, so you should ask a new question about it if you can't figure it out based on the links I gave you. Your original difficulty is from your `api` subdomain constraint. – Paul A Jungwirth Apr 18 '16 at 19:36
  • Well my point was, the issue you pointed out (re: binding to ipv6 addresses) is not exclusive to Puma alone. If the constraint is the issue, and not any of the web servers, why can't I access it with a direct request to `api.localhost:3000`? That seems to indicate to me that the issue is not just web server specific. – marcamillion Apr 19 '16 at 16:51
  • Assuming you mean `api.lvh.me:3000`, it's because your browser resolves the domain to `127.0.0.1`, but puma is only listening on `::1`. If you want to add `api.localhost` to your `/etc/hosts` you can force it to resolve to `::1` instead. – Paul A Jungwirth Apr 19 '16 at 16:58
  • So I just added this to my `/etc/hosts` - `api.localhost ::1` And I see progress now. Now when I go to http://api.localhost:3000 it actually hits my Rails server. I now get routed to the 'welcome#index' which is the `root_path` in my app. The issue is, when I go to http://api.localhost:3000/users/1 I get this in my server logs: `Started GET "/users/1" for ::1 at 2016-04-21 12:28:33 -0500 ActionController::RoutingError (No route matches [GET] "/users/1"):` So it still doesn't work. I agree with you that this is related to my route, but I can't figure out how to get the routing working. – marcamillion Apr 21 '16 at 17:29
  • I don't think Rails will consider `api.localhost` to have a subdomain at all because there is only one dot. Can you add `api.lvh.me` to your `/etc/hosts` instead and try that? – Paul A Jungwirth Apr 21 '16 at 18:21