I have a Rails API and after starting the server on the first request I am receiving the following error:
Unable to autoload constant Api::V1::UsersController, expected /Users/user/Sites/project/api/app/controllers/api/v1/users_controller.rb to define it
If I then continue to make requests everything loads as normal without errors.
I am using Ruby 2.7.1 and Rails 5.1.7 for just an API application.
I have the following routes defined:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
# User Routes
post 'create_user' => 'users#create'
get 'get_user' => 'users#show'
delete 'destroy_user' => 'users#destroy'
put 'update_user' => 'users#update'
# Login Route
post 'login' => 'user_token#create'
end
end
end
My folder structure is
project
- app
- controllers
- api_controller.rb
- application_controller.rb
- Api
- V1
- users_controller.rb
- models
- user.rb
The User's controller it is setup like so (I have also tried inline Api::V1::UsersController inheritance):
module Api
module V1
class UsersController < ApiController
before_action :authenticate_user, only: [:show, :update, :destroy]
def create
#user = User.new(user_params)
if #user.save
render json: { status: 200, data: #user }, status: :ok
else
render json: { status: 422 }, status: :unprocessable_entity
end
end
def show
render json: { status: 200, data: current_user }, status: :ok
end
def update
if current_user.update(user_params)
render json: { status: 200, data: current_user }, status: :ok
else
render json: { status: 422 }, status: :unprocessable_entity
end
end
def destroy
if current_user.delete
render json: { status: 200, data: {} }, status: :ok
else
render json: { status: 422 }, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation, :first_name, :last_name)
end
end
end
end
Api controller looks like this:
class ApiController < ActionController::API
include Knock::Authenticable
end
Application controller like this:
class ApplicationController < ActionController::API
end
User model looks like this:
class User < ApplicationRecord
has_secure_password
end
Any help would be much appreciated as this structure looks correct to me
Related
All, i am creating a rails api following a tutorial. I am running into issue using rails 6 and fast_jsonapi gem.
I have the following in my controller
module Api
module V1
class ArticlesController < ApplicationController
protect_from_forgery with: :null_session
def index
articles = Article.all
render json: ArticleSerializer.new(articles, options).serialized_json
end
def show
article = Article.find_by(slug: params[:slug])
render json: ArticleSerializer.new(article, options).serialized_json
end
def create
article = Article.new(article_params)
if article.save
render json: ArticleSerializer.new(article).serialized_json
else
render json: {error: article.error.messages}, status: 422
end
end
def update
article = Article.find_by(slug: params[:slug])
if article.update(article_params)
render json: ArticleSerializer.new(articles,options).serialized_json
else
render json: {error: article.error.messages}, status: 422
end
end
def destroy
article = Article.find_by(slug: params[:slug])
if article.destroy(article_params)
head :no_content
else
render json: {error: article.error.messages}, status: 422
end
end
private
def article_params
params.require(:articles).permit(:name, :image_url)
# params.permit(:name, :image_url)
end
#compond document, pass in optional hash with additionalresources
def options
#options ||= {include:%i[reviews]}
end
end
end
end
i am able to get Request, however, when i try to create a article for example with the following strong parameter
params.require(:articles).permit(:name, :image_url)
i get the following:
If i remove require and just add
params.permit(:name, :image_url)
Record is created but its empty
{
"data": {
"id": "21",
"type": "article",
"attributes": {
"name": null,
"image_url": null,
"slug": "article"
},
"relationships": {
"reviews": {
"data": []
}
}
}
}
Any assistance on this?
here is my review controller:
module Api
module V1
class ReviewsController < ApplicationController
protect_from_forgery with: :null_session
def create
review = Review.new(review_params)
if article.save
render json: ReviewSerializer.new(review).serialized_json
else
render json: {error: review.error.messages}, status: 422
end
end
def destroy
review = Review.find_by(params[:id])
if review.destroy
head :no_content
else
render json: {error: review.error.messages}, status: 422
end
end
private
def review_params
# params.require(:article).permit(:title, :description, :score , :article_id )
params.permit(:title, :description, :score , :article_id )
end
end
end
end
my routes
Rails.application.routes.draw do
root 'pages#index'
namespace :api do
namespace :v1 do
resources :articles, param: :slug
resources :reviews, only: [:create, :destroy]
end
end
get '*path', to: 'pages#index', via: :all
end
I'm trying to set up an API that logs in a user to my application and authenticates the user with a token. For this I am creating a new session.
Sessions controller
class Api::V1::SessionsController < Api::V1::BaseController
def create
user = User.where(email: params[:email]).first
if user&.valid_password?(params[:password])
render json: user.as_json(only: [:first_name, :last_name, :email]), status: :created
else
head(:unauthorized)
end
end
Base controller
class Api::V1::BaseController < ActionController::API
include Pundit
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
rescue_from StandardError, with: :internal_server_error
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
rescue_from ActiveRecord::RecordNotFound, with: :not_found
private
def internal_server_error(exception)
if Rails.env.development?
response = { type: exception.class.to_s, message: exception.message, backtrace: exception.backtrace }
else
response = { error: "Internal Server Error" }
end
render json: response, status: :internal_server_error
end
def user_not_authorized(exception)
render json: {
error: "Unauthorized #{exception.policy.class.to_s.underscore.camelize}.#{exception.query}"
}, status: :unauthorized
end
def not_found(exception)
render json: { error: exception.message }, status: :not_found
end
end
When I test the method, I receive AbstractController::DoubleRenderError, despite the user existing in the database. It points to the last line of the internal_server_error(exception) method of the BaseController.
Can someone help me understand why this code doesn't render the JSON of the user, when the user actually does exist in the database?
I'm getting a weird error in my api rails app:
AbstractController::DoubleRenderError in Api::V1::AdsController#restaurant
Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
I found this page https://api.rubyonrails.org/classes/ActionController/Base.html about double rendering, but I don't really get what they mean and how it applies to my situation.
I have this in my controller:
class Api::V1::AdsController < Api::V1::BaseController
before_action :set_ad, only: [ :show ]
def index
#ads = policy_scope(Subcategory.find_by(nombre: "Restaurantes").ads)
end
def restaurant
#restaurants = policy_scope(Subcategory.find_by(nombre: "Restaurantes").ads)
end
def show
end
private
def set_ad
#ad = Ad.find(params[:id])
authorize #ad
end
end
And this in my routes:
Rails.application.routes.draw do
devise_for :users
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :ads, only: [ :index, :show ] do
collection do
get 'restaurant', to: 'ads#restaurant'
end
end
end
end
root to: 'pages#home'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
And I have 3 views:
index.json.jbuilder:
json.array! #ads do |ad|
json.extract! ad,
:id,
:empresa,
:direccion_principal,
:tel,
:email_principal,
:web,
:facebook,
:instagram,
:twitter,
:isla
end
restaurant.json.jbuilder = the same as index
show.json.jbuilder:
json.extract! #ad, :id, :empresa, :direccion_principal
Can someone see the problem here?
EDIT:
AdPolicy:
class AdPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
end
def show?
true
end
end
Base Controller:
class Api::V1::BaseController < ActionController::API
include Pundit
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
rescue_from StandardError, with: :internal_server_error
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
rescue_from ActiveRecord::RecordNotFound, with: :not_found
private
def user_not_authorized(exception)
render json: {
error: "Unauthorized #{exception.policy.class.to_s.underscore.camelize}.#{exception.query}"
}, status: :unauthorized
end
def not_found(exception)
render json: { error: exception.message }, status: :not_found
end
def internal_server_error(exception)
if Rails.env.development?
response = { type: exception.class.to_s, message: exception.message, backtrace: exception.backtrace }
else
response = { error: "Internal Server Error" }
end
render json: response, status: :internal_server_error
end
end
I found the answer to my own question. It had to do with the BaseController. I forgot to add :restaurant to the after_action :verify_authorized and verify_policy_scoped. With the BaseController below it works now.
class Api::V1::BaseController < ActionController::API
include Pundit
after_action :verify_authorized, except: [:index, :restaurant]
after_action :verify_policy_scoped, only: [:index, :restaurant, :beach_restaurant, :cafe]
rescue_from StandardError, with: :internal_server_error
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
rescue_from ActiveRecord::RecordNotFound, with: :not_found
private
def user_not_authorized(exception)
render json: {
error: "Unauthorized #{exception.policy.class.to_s.underscore.camelize}.#{exception.query}"
}, status: :unauthorized
end
def not_found(exception)
render json: { error: exception.message }, status: :not_found
end
def internal_server_error(exception)
if Rails.env.development?
response = { type: exception.class.to_s, message: exception.message, backtrace: exception.backtrace }
else
response = { error: "Internal Server Error" }
end
render json: response, status: :internal_server_error
end
end
I am new to build Rails API from scratch (only API not web app) so I can build iOS app to connect to API. What I have an issue on my Rails API project doesn't allow me to have return JSON results, it always return HTML. Any idea what is wrong? Any suggestion appreciated. Thanks!
What I want to have like this:
{"code":12,"title":"User doesn't exist","status":404}
This is what I got (it returns in red header in HTML page):
ActiveRecord::RecordNotFound in Api::V1::UsersController#show
Couldn't find User with 'id'=5
Take a look at my scripts:
routes.rb (require JSON format):
Rails.application.routes.draw do
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :entries, except: [:new, :edit]
resources :users, except: [:new, :edit]
end
end
end
../app/controllers/api/v1/users_controller.rb
module Api
module V1
class UsersController < ApplicationController
before_action :set_user, only: [:show, :update, :destroy]
def show
if User.exists?(params[:id])
#user = User.find(params[:id])
render json: #user
else
render json: { code: 12, title: "User doesn't exist", status: 404 }, status: :not_found
end
.
.
.
Note: It is very strange how this show method above acting weird. Part 1 is working well to return JSON results but Part 2 returns HTML instead JSON results.
Part 1: render json: #user
Part 2: render json: { code: 12, title: "User doesn't exist", status: 404 }, status: :not_found
../app/controllers/api/v1/application_controller.rb
class ApplicationController < ActionController::API
rescue_from ActionController::ParameterMissing, with: :render_bad_request
rescue_from ActiveRecord::RecordNotSaved, :with => :access_denied
def render_bad_request(e)
render json: params, status: :bad_request
end
end
Use User.where(id: params[:id]).first instead User.exists?(). Is the same query and it going to be cached by Active Record.
Also, i think that you before_action method is the guilty. Try with this
protected
def set_user
#user ||= User.where(id: params[:id]).first
end
...
And in your controller
def show
if #user.present?
render json: #user
else
render json: { code: 12, title: "User doesn't exist", status: 404 }, status: :not_found
end
This code is for a UserList (a user can create a User To-Do List). This particular resource does not hold the list items, but just the title of the list, and the type of list.
class Api::V1::UserListsController < ApplicationController
respond_to :json
skip_before_filter :verify_authenticity_token
def index
if authenticate_user
user_lists = #current_user.user_lists
if user_lists
respond_with user_lists, each_serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not find user's lists."}, status: :not_found
end
else
render json: { error: "User is not signed in." }, status: :unauthorized
end
end
def show
if authenticate_user
user_lists = #current_user.user_lists
user_list = user_lists.find_by_id(params[:id])
if user_list
respond_with user_list, serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not find user's list."}, status: :not_found
end
else
render json: { error: "User is not signed in." }, status: :unauthorized
end
end
def create
if authenticate_user
user_list = #current_user.user_lists.new(user_list_params)
if (user_list.save!)
respond_with :api, :v1, #current_user, user_list, serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not create new User List."}, status: :unprocessable_entity
end
else
render json: { error: "User is not signed in." }, status: :unauthorized
end
end
def update
if authenticate_user
user_list = #current_user.user_lists.find_by_id(params[:id])
if (user_list.update_attributes(user_list_update_params))
respond_with :api, :v1, #current_user, user_list, serializer: Api::V1::UserListSerializer
#respond_with user_list, serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not update User List." }, status: :unprocessable_entity
end
end
end
private
def user_list_params
params.require(:user_list).permit(:user_id, :type_id, :title)
end
def user_list_update_params
params.require(:user_list).permit(:type_id, :title)
end
end
Now the update works when I PUT/PATCH... but I get a
Completed 204 No Content in 24ms (ActiveRecord: 4.3ms)
It's been about 4+ months since I've done any rails, and back then I was only just beginning to learn it.
1) Does anyone know why I'm not getting anything back? I know it's something to do with my respond_with line of code in update, but I'm not sure exactly what.
2) Can someone clarify to me the difference between the SHOW respond_with and the CREATE respond_with. I recall having an issue grasping this back then, and obviously now.
SHOW
respond_with user_list, serializer: Api::V1::UserListSerializer
CREATE
respond_with :api, :v1, #current_user, user_list, serializer: Api::V1::UserListSerializer
a) Why does create require :api and :v1 first, but show does not?
b) Why does create require the #current_user, but show does not?
Appendix: Here is my Serializer for reference
class Api::V1::UserListSerializer < ActiveModel::Serializer
attributes :id, :user_id, :type_id, :title
has_many :items, embed: :ids
end
I know this is 2 years too late, but after some digging, I found the empty response with the 204 is intentional (as mentioned above). If you use respond_with this will always be the case. A workaround would be to use render instead (example below):
class Api::V1::ItemsController < ApplicationController
respond_to :json
...
def update
#item = Item.find(params[:id]
if #item
#item.update_attribute(item_params)
render json: #item
end
end
...
end
You're not supposed to get anything back other than the 204. Any intelligent client does not need to receive back the data it just sent you -- it needs only confirmation that the data was persisted.
Do not mistakenly pass your class Api::V1::UserListSerializer as a key/value pair (Hash form). You will get an error including the text class or module needed. It should look like this:
serialize :some_array, Api::V1::UserListSerializer
Or, perhaps clearer would be:
serialize(:some_array, Api::V1::UserListSerializer)
You miss one param and you are rendering an object class with no content : 204 - No Content
That may seem obvious, but it is common to be in the habit of passing things as a key/value pair.
One improve:
before_action :authenticate_user, only: [:create, :show, :update, ...]
https://apidock.com/rails/ActiveRecord/Base/serialize/class
def update
#item = Item.find(params[:id])
respond_with(:api, :v1, #item) do |format|
if #item.update(item_params)
format.json { render json: #item}
else
format.json { render json: {error: #item.errors.full_messages}}
end
end
end