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
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 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
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 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
I'm testing some gems since I'm about to create an API for a iOS app. This time I'm using Rails-API and Devise for user registrations. I followed this gist that I forked from a forked-forked gist, I did some changes and a User can register and login, but, when I use before_filter :authenticate_api_user! on a controller, after login the current_api_user is nil. According to this from the source code of divise, I a use devise_for inside a namespace then everything changes. This is my code.
Routes:
Todo::Application.routes.draw do
namespace :api do
devise_for :users
resources :users, only: [:index, :show]
resources :videos, only: [:create]
get "/users/show", to: "users#show"
end
end
app/api/sessions_controller.rb
class Api::SessionsController < ApplicationController
before_filter :authenticate_api_user!, except: [:create]
before_filter :ensure_user_login_param_exists, only: [:create]
# before_filter :ensure_email_param_exists, only: [:create]
# before_filter :ensure_password_param_exists, only: [:create]
respond_to :json
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(:api_user, resource)
#resource.ensure_authentication_token!
render json: { success: true, email: current_api_user.email }, status: :created
return
end
invalid_login_attempt
end
.
.
.
end
app/api/registrations_controller.rb
class Api::RegistrationsController < ApplicationController
respond_to :json
def create
user = User.new(user_params)
if user.save
render json: user.as_json(email: user.email), status: :created
return
else
warden.custom_failure!
render json: user.errors, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
I hope someone can help me. By the way I'm really accepting any advice about the best way to create an API using rails. It's my first time.
Thank you in advance.