Am using Rails 3.0.19 and JBuilder Gem 2.0.6 to render JSON responses.
JBuilder: https://github.com/rails/jbuilder
Following is the code am using to send error-messages for a specific API.
render :json, :template=>"/api/shared/errors.json.jbuilder", :status=> :bad_request
For some reason, the client receives 200-ok status. While, I have expected 400 (bad_request).
Any help, please?
Here is my code in detail:
def render_json_error_messages
#render :template=> "/api/shared/errors.json.jbuilder", :status=> :bad_request, :formats => [:json]
respond_to do |format|
format.json {
render :template=> "/api/shared/errors.json.jbuilder", :status=> 400
}
end
end
And in a before_filter method, I use render_json_error_messages
This works:
controller
def some_action
render status: :bad_request
end
some_action.jbuilder
json.something "test"
Try rendering jbuilder to string then set the status... works in Rails 4.1.4
jstr = render_to_string( template: 'api/shared/index.jbuilder', locals: { nodes: #nodes})
respond_to do |format|
format.html
format.json { render json: jstr, status: :bad_request }
end
Else following also works
format.json { render template: 'api/shared/index.jbuilder', status: 404 }
I don't know about Rails 3.0, but I was able to provide the appropriate status by simply adding a respond_to block to the controller action. As an example, I have an create like so:
def create
# ... create logic
respond_to do |format|
format.html
format.json {
render status: :created, success: true
}
end
The above code sets my status code to 201 and renders app/views/orders/create.json.jbuilder
Hope that helps.
maybe reverse the thinking:
#pictures = ...
respond_to do |format|
format.html
format.json do
if #error.present? # #error contains some error message
render json: #error, status: :unprocessable_entity
else
render template: 'api/shared/index.jbuilder'
end
end
api/shared/index.jbuilder
json.array! #pictures, :id, :filename, :width, :height
works in Rails5.1.4
Related
In my heroku RoR app,I have a controller in which I support JSON requests. There I have the following functions:
def mssg_as_json
#message = Message.new
#message.text = params.require(:messages)
#message.save
string = "http://link.com/"
#message.url = string + #message.id.to_s
#message.save
render json: { url: #message[:url] }
end
def return_mssg_as_json
if #message = Message.find_by(id: params[:id])
render json: { message: #message[:text] }
else
render json: {errors: :not_found}, status: :not_found
end
end
I want to support XML requests too. My idea is to somehow convert the XML to JSON but I have no idea how. How can I modify the code to support both XML and JSON?
P.S.
My routes are:
get "messages/api" => "messages#return_mssg_as_json"
post "messages/api" => "messages#mssg_as_json"
The requests are send to main_url/messages/api
So, you should take advantage of the standard routes and actions. In routes.rb, you might do something like:
Rails.application.routes.draw do
resources :messages
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :messages
end
end
end
Note that your controller is now nested inside api/v1. That allows you to identify the path as an api and to maintain versions over time. That solid's practice. Also note that you have a standard messages resource for your web app.
Then, your controller would look like:
class Api::V1::MessagesController < Api::V1::BaseController
def create
#message = Message.new(message_params)
respond_to do |format|
if #message.save
#message.update(url: "http://link.com/#{#message.id}")
format.json { render json: create_hsh, status: :ok }
format.xml { render xml: create_hsh, staus: :ok }
else
format.json { render json: #message.errors, status: :unprocessable_entity }
format.xml { render xml: #message.errors, status: :unprocessable_entity }
end
end
end
def show
respond_to do |format|
if #message = Message.find_by(id: params[:id])
format.json { render json: show_hsh, status: :ok }
format.xml { render xml: show_hsh, status: :ok }
else
format.json { render json: {errors: :not_found}, status: :not_found }
format.xml { render xml: {errors: :not_found}, status: :not_found }
end
end
end
private
def create_hsh
#message.attributes.with_indifferent_access.slice(:url)
end
def show_hsh
attr = #message.attributes.with_indifferent_access
attr.slice(:foo, :bar).merge!(message: attr[:text])
end
def message_params
params.require(:message).permit(:text)
end
end
Note that this controller inherits from Api::V1::BaseController. That would be a controller that you set up to do api-relevant client authentication (key/token checking, etc.). Something, perhaps, like:
class Api::V1::BaseController < ActionController::API
before_action :authorize
def authorize
# do your checks in a way that results in a variable #authorized?
render(json: {errors: :unauthorized}, status: :unauthorized) unless #authorized?
end
end
So, now you're using a single controller action to respond to all format types (that you elect to offer). Then, you clients would post something like:
http://your.api.com/messages.json
To get a json response. Or:
http://your.api.com/messages.xml
To get an xml response. You might notice the bit that says: namespace :api, defaults: {format: 'json'} do. This means that your client could call:
http://your.api.com/messages
and it will default to the json format.
Now you don't have a bunch of random endpoints (like mssg_as_json), just the regular RESTful ones that your clients will expect. Your API clients will love you for that.
You'll note that show_hsh is the accepted code from your earlier question.
Let's assume I have a controller action that whenever you try to access it via a js format, I want to render nothing.
def no_js_please
# there isn't a no_js_please partial or view
respond_to do |format|
format.html { # render a template }
format.json { render json: {valid: true} }
format.js { render nothing: true, status: :not_found }
end
end
When you access the endpoint via html or JSON, it works just fine. When you try to access it via JS it doesn't work. However, it does actually execute the block, because if I add a binding.pry within the js block, it gets picked up.
However, this does work:
def no_js_please
if request.format.js?
render nothing: true, status: :not_found
end
end
So what's the difference between the two?
EDIT
As a human mentioned below, what's the actual error? So, in this particular case, it raises a ActionController::InvalidCrossOriginRequest.
Use "head" instead of "render nothing: true". The latter is actually deprecated in Rails 5+ anyway.
def no_js_please
respond_to do |format|
format.html { # render a template }
format.json { render json: {valid: true} }
format.js { head :not_found }
end
end
This will get you the result you're looking for and it will be one less line to change when you upgrade.
How can I edit / destroy an attachment uploaded via refile within a rails app? I can successfully create attachments from curl, but I can't figure out how to delete them. I read the refile documentation on removing attachments here, https://github.com/refile/refile#removing-attached-files but when I load the following URL http://localhost:3000/api/csv_files/1/edit it is sending a JSON response to my browser, and the rails app is not rendering the edit.html.erb template instead it renders a edit.json.jbuilder template.
# csv_files_controller.rb
def edit
#csv_file = CsvFile.find(params[:id])
respond_to do |format|
format.html { render html: #csv_file.as_html(only: [:id]) }
format.json { render json: #csv_file.as_json(only: [:id]) }
# format.json { render action: 'edit'}
# else
# format.html { render action: 'edit' }
# format.json { render json: #csv_file.errors, status: :unprocessable_entity}
end
end
# DELETE /csv_files/1
# DELETE /csv_files/1.json
def destroy
#csv_file = CsvFile.find(params[:id])
if #csv_file.destroy
render :json => { :head => ok }, status: 200
else
render json: {error: "csv file could not be deleted."}, status: 422
end
end
private
def csv_params
# binding.pry
params.permit(:csv_file, :csv_file_filename, :csv_file_id, :csv_file_content_type, :remove_csv_file)
end
end
(I've broken out the 2nd question that originally was part of this post into a separate post)
I am creating a product landing page with Rails in which users can enter their email address to be notified when the product launches. (Yes, there are services/gems etc that could do this for me, but I am new to programming and want to build it myself to learn rails.)
On submit of the form, if there are errors, the app currently redirects to '/invites' I would like to instead display error messages on the same page/URL as the original form? (In my case, the form is located at root while the error messages are displaying at '/invites')
I have read the Rails Guide on Routes and numerous stackoverflow posts on handling form errors nothing I've found seems to answer the question I have.
Update: Based on the reply from #rovermicrover I would like to clarify that, while I'm open to an Ajax solution, I'm fine with a page refresh that displays the error message. (I was not able to get the recommendation by #rovermicrover to function as desired - see my response to that solution below for more details.)
What I did:
Invite model:
class Invite < ActiveRecord::Base
attr_accessible :email
validates :email, :presence => {:message => "Please enter an email address."}
end
My routes file:
SuggestionBoxApp::Application.routes.draw do
root to: 'invites#new'
resources :invites
end
This is what I have in the Invites controller (I've only included the actions I'm referencing: new, create, show - it's basically the default of what Rails might generate):
class InvitesController < ApplicationController
def show
#invite = Invite.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #invite }
end
end
def new
#invite = Invite.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #invite }
end
end
def create
#invite = Invite.new(params[:invite])
respond_to do |format|
if #invite.save
format.html { redirect_to #invite }
format.json { render json: #invite, status: :created, location: #invite }
else
format.html { render action: "new" }
format.json { render json: #invite.errors, status: :unprocessable_entity }
end
end
end
end
Please let me know if there is any additional info I can provide in helping to answer this question. Thanks!
Make the form 'remote'
form_for #invite, :remote => true
....
Then in the controller
def create
#invite = Invite.new(params[:invite])
respond_to do |format|
if #invite.save
format.html { redirect_to #invite }
format.js { render :action => 'create_suc'}
else
format.html { render action: "new" }
format.js { render :action => 'create_fail' }
end
end
end
/invites/create_suc.js.erb
$('#errors').remove()
$('#new_invite').prepend("<div class='Thanks'>Thanks for signing up</div>")
$('#new_invite').hide("")
/invites/create_fail.js.erb
$('#new_invite').html('<%= escape_javascript render("form", :invite => #invite) %>');
Forms is a partial with your.... form in it, and also the handling of all errors on #invite.
There is a way to do this without resorting the making the form submit "remote", from a pure Ruby on Rails perspective. However, you can do this only if the browser has enabled cookies.
The idea is to save the form data in the session information in case of an error.
Just remember to delete the session data in case of success.
def new
#invite = Invite.new(session[:invite])
respond_to do |format|
format.html # new.html.erb
format.json { render json: #invite }
end
end
def create
#invite = Invite.new(params[:invite])
respond_to do |format|
if #invite.save
session.delete(:invite)
format.html { redirect_to #invite }
format.json { render json: #invite, status: :created, location: #invite }
else
session[:invite] = params[:invite]
format.html { render action: "new" }
format.json { render json: #invite.errors, status: :unprocessable_entity }
end
end
end
respond_to do |format|
if #user.save
format.js { render :nothing => true, :status => :ok, :location => #user }
else
format.js { render :json => #user.errors, :status => :unprocessable_entity }
end
end
All options I've tried (like putting respond_to :js at the top of controller, etc) don't quite work the way as in this.
Rails 3 Format:
Use respond_to :json and respond_with(#user)
respond_to :json # You can also add , :html, :xml etc.
def create
#user= User.new(params[:user])
#---For html flash
#if #user.save
# flash[:notice] = "Successfully created user."
#end
respond_with(#user)
end
# Also, add :remote => :true, :format => :json to the form.
Try using format.json instead of format.js in your controller and :remote => :true, :format => :json in corresponding form.
Though, I'm not quite sure whether format.json or format.js should be used in that case. As default scaffolding from Rails 3 generates controllers with format.json and you're doing render :json in response I believe format.json is the right way to go. And format.js should be used when you return a piece of JS that should be executed.
The URL your are requesting should end with .json like this: /controller/action.json
If that is not possible:
You should set the 'accepts' parameter to 'application/json' while sending the ajax request.
Search for how to use 'accepts' here: http://api.jquery.com/jQuery.ajax/
And in the server side:
format.json { render :json => #user.errors, :status => :unprocessable_entity }