javascriptruby-on-railsroutesrails-apijavascript-namespaces

double namespaced routing issues with fetch


Hello> Im creating a Javascript SPA/Rails Api that has books and users. I using namespace in my routes,along with serializers for each model. Im not sure why im getting this error when trying to do a post fetch request for a user. Im not sure if i will have the same problem with my books. Belo you can see where in my controller at the top have Api::V1....etc for each controller. I then in my routes have my namespace routes. My rails console is running as well

Routing Error uninitialized constant Api

my controller

class Api::V1::UsersController < ApplicationController

    def index
        users=User.all
        render json: users
    end 

    def create
        if User.find_by(:name=> user_params[:name])
            user=User.find_by(:name=>user_params[:name])
            redirect_to "/api/v1/users/#{user.id}"
        else 
            user = User.create(user_params)
            user.save!
            render json: user
        end 
    end 

    def show
        user = User.find_by(:id => params[:id])
        render json: user
    end 

    private

    def user_params
        params.require(:user).permit(:name)
    end
end
class Api::V1::BooksController < ApplicationController

    def index
        books = Book.all
        render json: books
    end 

    def create
        book = Book.create(book_params)
        book.save!
        render json: book
    end 

    def destroy
        book=Book.find_by(:id => params[:id]).destroy
        render json: book
    end 

    private

    def book_params
        params.require(:book).permit(:title,:author,:review,:rating,:user_id)
    end 
end

ROUTES

Rails.application.routes.draw do






  namespace :api do
    namespace :v1 do
      resources :books
    end
  end
end
//POST fetch for creating a User
    static createUser(user) {
        let newUserForm = document.getElementById('new-user-form')
        newUserForm.addEventListener('submit', function(e){
            e.preventDefault();
            fetch('http://localhost:3000/api/v1/users', {
                method: 'POST',
                headers: {
                    'Content-Type' : 'application/json',
                    'Accept' : 'application/json'
                },
                body: JSON.stringify({
                    user: {
                        name: e.target.children[1].value
                    }
                })
            })
                .then(res => {
                    if (!res.ok) {
                        throw new Error(); // Will take you to the `catch` below
                    }
                    return res.json();
                })
                .then (user => {
                    let newUser = new User(user)
                    console.log(user)
                    newUser.displayUser();
                })
                .catch(error => {
                    console.error('User class Error', error)
                })
            })
    }

Solution

  • Define (and reopen) namespaced classes and modules using explicit nesting. Using the scope resolution operator can lead to surprising constant lookups due to Ruby’s lexical scoping, which depends on the module nesting at the point of definition.

    module Api
      module V1
        class UsersController < ApplicationController
          # ...
        end
      end 
    end
    

    While you could naively belive that class Api::V1::UsersController does the same thing it does not as it requires the Api module to be defined at the time the class is loaded and does not properly set the module nesting which will lead to suprising constant lookups. For example when you use User Ruby will not find Api::User or Api::V1::User as could be expected.

    There are however many more issues with this code as its riddled with poor api design descisions - potential nil errors (through the use of find_by instead of find). It should really look like this:

    # no need to repeat yourself
    namespace :api do
      namespace :v1 do
        resources :users
        resources :books
      end 
    end
    
    # config/initializers/inflections.rb
    ActiveSupport::Inflector.inflections(:en) do |inflect|
      inflect.acronym 'API'
    end
    
    # Acronyms should be all-caps
    # https://github.com/rubocop-hq/ruby-style-guide#camelcase-classes
    module API
      module V1
        class UsersController < ApplicationController
          
          # GET /api/v1/users
          def index
            users = User.all
            render json: users
          end 
    
       
          # POST /api/v1/users
          def create
            user = User.new(user_params)
            if user.save
              render json: user, 
                     status: :created
            else
              render json: { errors: user.errors.full_messages }, 
                     status: :unprocessable_entity
            end
          end 
          
          # GET /api/v1/users/1
          def show
            # Use .find instead of find_by as it will return a
            # 404 - Not Found if the user is not found
            user = User.find(params[:id])
            render json: user
          end 
          
          private
          
          def user_params
            params.require(:user).permit(:name)
          end
        end
      end 
    end
    
    module API
      module V1
        class BooksController < ApplicationController
          # GET /api/v1/books
          def index
            books = Book.all
            render json: books
          end
    
          # POST /api/v1/books
          def create
            book = Book.new(book_params)
            if book.save
              render json: book, 
                     status: :created
            else 
              render json: { errors: book.errors.full_messages }, 
                     status: :created
            end
          end
    
          # DELETE /api/v1/books/1
          def destroy
            book = Book.find(params[:id])
            book.destroy
            head :ok 
          end
    
          private
    
          def book_params
            params.require(:book)
                  .permit(:title, :author, :review, :rating, :user_id)
          end
        end
      end
    end
    

    When creating a resource you should return a 201 CREATED response if it was successful and either include the resource in the response body or provide a location header which tells the client where they can find the resource.

    If its not successful you should return a status code such as 422 Unprocessable Entity which tells the client that the server was not able to process the request with the given parameters.