I have a Rails controller, and I'm using whitelisted params to create new items:
def course_discussion_comment_params
params.require(:course_discussion_comment).permit!
end
Most of the time this works correctly and the logs show me the params hash as
Parameters: {"body"=>"xxx", "course_id"=>"xxx", "course_discussion_id"=>"xxx", "course_discussion_comment"=>{"body"=>"xxx"}}
However, occasionally the params hash doesn't seem to get parsed correctly by Rails, and the request fails, logging a params hash using application instead of course_discussion_comment, like below:
Parameters: {"body"=>"xxx", "course_id"=>"xxx", "course_discussion_id"=>"xxx", "application"=>{"body"=>"xxx"}}
...
ActionController::ParameterMissing (param is missing or the value is empty: course_discussion_comment):
I can't seem to find anything causing it on my end, the issue is intermittent and occurs on other controllers too, but it is consistent in that it has happened multiple times, and in particular this controller seems to be affected the worst, happening every few days and only getting resolved when the server is restarted (it's on Heroku, so dyno is restarted daily).
Any ideas of what might be going on? Here is the full controller:
class CourseDiscussionCommentsController < ApplicationController
before_action :load_course
before_action :load_course_discussion
before_action :set_course_discussion_comment, only: [:update, :destroy]
before_action {
in_course(params[:course_id])
}
before_action only: [:update, :destroy] {
current_user(#course_discussion_comment[:from][:_id])
}
def create
course_discussion_comment_params[:from] = {
_id: #authenticatedUser[:id],
firstName: #authenticatedUser[:firstName],
lastName: #authenticatedUser[:lastName],
}
course_discussion_comment_params[:deleted] = false
#course_discussion_comment = #course_discussion.course_topic_comments.build(course_discussion_comment_params)
if #course_discussion_comment.save
DiscussionActionMailer.comment_added(request.base_url, #course, #authenticatedUser, #course_discussion, #course_discussion_comment).deliver_now
render json: #course_discussion_comment, status: 200
else
render json: #course_discussion_comment.errors, status: 422
end
end
def update
if #course_discussion_comment.update(course_discussion_comment_params)
render json: #course_discussion_comment, status: 200
else
render json: #course_discussion_comment.errors, status: 422
end
end
def destroy
params = {
:deleted => true
}
if #course_discussion_comment.update(params)
render json: #course_discussion_comment, status: 200
else
render json: #course_discussion_comment.errors, status: 422
end
end
private
def course_discussion_comment_params
params.require(:course_discussion_comment).permit!
end
def load_course
#course = Course.find(params[:course_id])
end
def load_course_discussion
#course_discussion = #course.course_topics.find(params[:course_discussion_id])
end
def set_course_discussion_comment
#course_discussion_comment = #course_discussion.course_topic_comments.find(params[:id])
end
end
Related
I have to make a query to order an array depending which param the user sends (ASC, DESC) but it is returning:
Started GET "/api/v1/productions" for 127.0.0.1 at 2021-10-22 23:24:18 -0300
Processing by Api::V1::ProductionsController#index as */*
Completed 500 Internal Server Error in 33ms (ActiveRecord: 0.0ms | Allocations: 5946)
ArgumentError (Direction "" is invalid. Valid directions are: [:asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]):
app/controllers/api/v1/productions_controller.rb:9:in `index'
Here's the controller:
class Api::V1::ProductionsController < ApplicationController
before_action :set_production, only: [:show, :update, :destroy]
MAX_PAGINATION_LIMIT = 10
has_scope :by_title
has_scope :by_genre
def index
#productions = (apply_scopes(Production.limit(limit).offset(params[:offset]))).order(created_at: #order)
render json: ProductionsRepresenter.new(#productions).as_json
end
def show
render json: ProductionRepresenter.new(#production).as_json
end
def create
#production = Production.new(production_params)
if #production.save
render json: ProductionRepresenter.new(#production).as_json
else
render json: #production.errors, status: :unprocessable_entity
end
end
def update
if #production.update(production_params)
render json: ProductionRepresenter.new(#production).as_json, status: 200
else
render json: #production.errors, status: :unprocessable_entity
end
end
def destroy
#production.destroy
end
private
def limit
[params.fetch(:limit, MAX_PAGINATION_LIMIT).to_i].min
end
def production_params
params.permit(:title, :released_date, :score, :image, :production_type, {:character_ids => []}, {:genre_ids => []})
end
def set_production
#production = Production.find(params[:id])
end
def order_params
#order = params.fetch(:order, "ASC")
end
end
I have another controller called for Characters, which is almost the exact same and it does order by properly. I've tried multiple things like replacing the line with #productions = (Production.all).order(created_at: #order) (with or without brackets). If I literally put the string like order(created: 'asc') it works, but that's not what I need, and I can't figure out what's wrong.
So I changed the order_params to
def order
params.fetch(:order, "ASC")
end
and replaced it in the controller
#productions = (apply_scopes(Production.limit(limit).offset(params[:offset]))).order(created_at: order)
and it somewhat worked, but I'm not sure if that's the best way to solve it. If there is, please let me know
i know this error AbstractController::DoubleRenderError (Render and/or redirect were called multiple times in this action occur when render multi time
but already use solution and return
class FrequentMethodController < ApplicationController
def post_exist?(post_id)
post = Post.find_by_id(post_id)
render_json('post_not found', 400, 'msg') unless post
return post
end
def render_json(data, status_code, main_key = 'data')
render json: { "#{main_key}": data }, status: status_code and return
end
end
class PostController < FrequentMethodController
def view_post
post_id = params[:post_id]
post = post_exist?(post_id)
# test code
render_json(post, 1, 'data')
end
end
and try using render direct but not work
render :json => { :msg => 'post not found ' },:status => 400 and return
Use 'rails', '5.1.4'
class FrequentMethodController < ApplicationController
def post_exist?(post_id)
post = Post.find_by_id(post_id)
render_json('post_not found', 400, 'msg') unless post
return post # <------------------------------------------------------------ THIS
end
def render_json(data, status_code, main_key = 'data')
render json: { "#{main_key}": data }, status: status_code and return # <--- AND THIS
end
end
Rails sees a return first in the render_json method and, when it exits that method it sees another return in post_exists?
Try moving your return outside of the render_json method:
class FrequentMethodController < ApplicationController
def post_exist?(post_id)
post = Post.find_by_id(post_id)
render_json('post_not found', 400, 'msg') and return unless post
return post
end
def render_json(data, status_code, main_key = 'data')
render json: { "#{main_key}": data }, status: status_code
end
end
You could also eliminate the and return with an if statement:
class FrequentMethodController < ApplicationController
def post_exist?(post_id)
post = Post.find_by_id(post_id)
if post
return post
else
render_json('post_not found', 400, 'msg')
# if this is the only place render_json is used, I wouldn't bother making it a method
# render json: { 'msg': 'post_not found' }, status: 400
end
end
def render_json(data, status_code, main_key = 'data')
render json: { "#{main_key}": data }, status: status_code
end
end
UPDATE:
There's actually a TRIPLE return going on here when we start with PostController.
Rails visits PostController#view_post
Rails goes to FrequentMethodController#post_exists?
While still inside FrequentMethodController#post_exists?, Rails goes to FrequentMethodController#render_json? and finds return #1
Rails goes back up to FrequentMethodController#post_exists?and finds return #2
Rails goes back to PostController#view_post and sees render_json (AGAIN!!)
Rails goes to FrequentMethodController#post_exists? and finds return #3
This code is spaghetti.
If FrequentMethodController is really just a helper file, then I think it should never have a return. Keep early returns to a minimum, and only in the main controller for the model.
Early returns are helpful to clean up complicated and nested if statements and potentially make code more readable, but you don't have that problem here. In fact, all these returns have made your code brittle, unpredictable, and overly complicated.
Overall, I think FrequentMethodController is a BAD idea.
I think post_exist?(post_id) should return either true or false.
I think render_json is simple enough that it shouldn't be it's own method.
AND, you've hijacked the normal CRUD structure
I would do something this instead:
class PostController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
# changed name from view_post
def show
if #post
render #post.as_json
else
render json: { 'msg': 'post_not found' }, status: 400
end
end
private
def set_post
#post = Post.find(params[:id])
end
end
Note that the above code has NO explicit returns and requires no manual helper files.
try this
def view_post
post_id = params[:post_id]
if post = post_exist?(post_id)
# test code
render_json(post, 1, 'data')
end
end
I have this following controller for my application:
class Api::BaseApiController< ApplicationController
before_action :parse_request, :authenticate_member_from_token!
def index
render nothing: true, status: 200
end
protected
def authenticate_member_from_token!
if !request.headers[:escambo_token]
#member = Member.find_by_valid_token(:activate, request.headers['escambo_token'])
if !#member
render nothing: true, status: :unauthorized
end
end
end
Then, I have another controller that inherits from that Controller:
class Api::CategoryController < Api::BaseApiController
before_action :find_category, except: [:index]
def index
#category = Category.all
puts(#category)
render json: #category
end
But the controller is allowing requests without the token.
EDIT 1: for some reason the index action started to working normally. But still not doing the validation for the token.
EDIT 2: fixing method from private to protected
Your code needs to render :unauthorized if the token is missing, OR invalid. In other words, you need the code to be along the lines of:
def authenticate_member_from_token!
unless Member.find_by_valid_token(:activate, request.headers['escambo_token'])
render nothing: true, status: :unauthorized
end
end
However, with this code you may find yourself double-rendering in the controller. A cleaner approach could be to instead raise an exception, then rescue from it and render appropriately - e.g.
EscamboTokenInvalid = Class.new(StandardError)
rescue_from EscamboTokenInvalid, with: :escambo_unauthorized
def authenticate_member_from_token!
unless Member.find_by_valid_token(:activate, request.headers['escambo_token'])
raise EscamboTokenInvalid
end
end
def escambo_unauthorized
render nothing: true, status: :unauthorized
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