JS format in the controller - ruby-on-rails

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.

Related

Creating 2 Models in Controller Action With Transaction - Rails 4

There are multiple answers that explain how you can have nested resources, however, my use case is a bit different.
Batches belong to orders and an order has many batches.
I can understand how it works if you have a form for an order and can create batches within that form, but cannot comprehend a good way for my situation.
I have a form for a nested resource (batch) where the parent (order) may or may not exist. They may select whether or not it exists via radio buttons. If it exists, they then just simply select which order it belongs to .. simple. If it doesn't exist, I show fields for the order and submit order params alongside of batch params. I want to make sure to rollback the order creation if the batch does not save.
Here is the code I have thus far.
def create
#batch = Batch.new(batch_params)
Batch.transaction do
if params[:new_order] == "newOrder"
#order = Order.new(order_params)
#order.project_id = params[:batch][:project_id]
begin
#order.save!
rescue
respond_to do |format|
format.html { render action: 'new' }
format.json { render json: {order: #order.errors}, status: :unprocessable_entity }
format.js { render json: {order: #order.errors}, status: :unprocessable_entity }
end
raise ActiveRecord::Rollback
return
end
##batch.order_id = #order.id
end
respond_to do |format|
begin
#batch.save!
format.html { redirect_to #batch, notice: 'Batch was successfully created.' }
format.json { render json: #batch }
format.js { render json: #batch }
rescue
binding.pry
raise ActiveRecord.Rollback
format.html { render action: 'new' }
format.json { render json: {batch: #batch.errors}, status: :unprocessable_entity }
format.js { render json: {batch: #batch.errors}, status: :unprocessable_entity }
end
end
end
end
This isn't behaving quite like I want it and seems quite ugly. I have a feeling I'm making it more difficult than I need to. What's the best approach in a situation like this? Much appreciated!
It seems like this is a great opportunity to use a Service Object: https://www.engineyard.com/blog/keeping-your-rails-controllers-dry-with-services .
This pattern is very useful for keeping Model and Controllers clean, and making sure those parts of the application are keeping to the Single Responsibility Principle.
What I would do in this case is create a service class called CreateBatch that takes in the parameters and performs the correct logic for each case. You can then render the correct output in the controller. This will also help clean up the conditionals and early returns you have.
For example:
# app/controllers/batches_controller.rb
def create
project_id = params[:batch][:project_id]
new_order = params[:new_order]
result = CreateBatch.new(new_order, batch_params, order_params, project_id).call
if result.errors
# handle errors with correct format
else
# handle successful response with correct format
end
end
# app/services/create_batch.rb
class CreateBatch
def initialize(new_order, batch_params, order_params, project_id)
#new_order = new_order
#batch_params = batch_params
#order_params = order_params
#project_id = project_id
end
def call
if new_order?
create_new_order
else
add_batch_to_existing_order
end
end
private
def new_order?
#new_order
end
def create_new_order
order_params = #order_params.merge(project_id: #project_id)
Order.save(order_params)
end
def add_batch_to_existing_order
Batch.create(#batch_params)
end
end
I did not run this so it may take a bit of tweaking to work, however, I hope it's a good starting point. One of the awesome things about this refactor is you now have 1 conditional for the logic and 1 conditional for the response, no need for adding in Transaction blocks, and no early returns. It may make sense to break the call method into 2 different methods that you can call from the controller. Using service classes like this makes the code much easier to unit test as well.
Why not move error handling and response rendering outside of the transaction?
def create
#batch = Batch.new(batch_params)
Batch.transaction do
if params[:new_order] == "newOrder"
#order = Order.new(order_params)
#order.project_id = params[:batch][:project_id]
#order.save!
#batch.order_id = #order.id
#batch.save!
end
end
respond_to do |format|
format.html { redirect_to #batch, notice: 'Batch was successfully created.' }
format.json { render json: #batch }
format.js { render json: #batch }
end
rescue StandardError => error
#error = error
format.html { render action: 'new' }
format.json { render json: {error: #error, batch: #batch.errors}, status: :unprocessable_entity }
format.js { render json: {error: #error, batch: #batch.errors}, status: :unprocessable_entity }
end
It's still quite complex, but it's definitely more readable. The next step would be to extract the whole transaction block to a service.

Redirect to another page in Ruby on Rails

I want to redirect to another page admin_antenna_reader_rfids_path at the end of the create method. I did:
def create
#antenna_reader_rfid = AntennaReaderRfid.new(antenna_reader_rfid_params)
if #antenna_reader_rfid.save
render json: {status: true}
redirect_to admin_antenna_reader_rfid_path(q#antenna_reader_rfid)
else
render json: {errors: #antenna_reader_rfid.errors.full_messages, status: false}
end
end
I get an error AbstractController :: DoubleRenderError:
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".
How can I solve this?
You have to remove the line render json: {status: true} as currently you're trying to make your controller render a json and redirect to an HTML page at the same time. You have to pick one.
To handle multiple request format, you can use respond_to
if #antenna_reader_rfid.save
respond_to do |format|
format.json { render json: { status: true } }
format.html { redirect_to where_you_want_path }
end
else
# same way as above
end
Within the respond_to block, you can render all the request formats as you want, then based on the request header, the controller will choose the corresponding logic to respond to you.
You can't render nor return more than once in a method.

Getting status: code on ajax response in a .js.erb file on Rails

I've just started to play with Rails applications and I'm trying to use Ajax on a form, but I don't know how to catch the status code inside a js.erb file. I'm following this tutorial: http://guides.rubyonrails.org/working_with_javascript_in_rails.html
On my Users controller I have a code for my update method:
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.js {}
format.json { render json: #user, status: :created, location: #user}
else
format.html { render action: 'edit' }
format.js {}
format.json { render json: #user, status: :unprocessable_entity }
logger.debug #user.response_errors
end
end
I've created a update.js.erb file inside my views/users/ folder and is very easy to debug the #user var, but I don't know how to get the status code setted on my method.
Sorry if it's a stupid question, but I'm new with rails and I'm trying to follow all the frameworks concepts to the letter and I don't know the best pratices to create responses to Ajax requests.
What I'm trying to achieve is something like this:
#on my update.js.erb
if( status == 'created' ) {
alert( 'Ok, created' )
} else {
alert( 'Something wrong happened' )
}
I appreciate any help.
Option 1: Check Validity Inside update.js.erb
This is the option that I recommend in most cases.
update.js.erb is an ERB template whose result is a JavaScript code to evaluate on the client. In this case, you can make it look like:
<% if #user.valid? %>
alert('Ok, created');
<% else %>
alert('Something wrong happened');
<% end %>
The decision which alert to displays happens server-side. The client receives either:
alert('Ok, create');
or
alert('Something wrong happened');
depending on the status of #user.
Option 2: Two separate js.erb files
You can split your update.js.erb into two files. update.js.erb should contain the happy path code:
alert('Ok, create');
update-error.js.erb should contain some error handling:
alert('Something wrong happened');
Then you decide which one to display in your controller:
respond_to do |format|
if #user.save
# ...
format.js {}
# ...
else
# ...
format.js { render 'update-error' }
# ...
end
end
I would try to do:
format.js {render json: {#user, status: :created, location: #user}}

Rails, respond_to blocks and |format|

Rails scaffold generated the following:
respond_to do |format|
if #student.save
format.html { redirect_to #student, notice => 'Student was successfully created.' }
format.json { render :show, status: :created, location: #student }
else
format.html { render :new }
format.json { render json: #student.errors, status: :unprocessable_entity }
end
end
After reading this I understand how the respond_to is working (sort of), but I don't get what format is doing. Shouldn't it be either format.html or format.json and not both? What are these two lines actually doing?
format.html { render :new }
format.json { render json: #student.errors, status: :unprocessable_entity }
Is there an implied if in there? Is it something like
if (format == html) {}
if (format == json) {}
Side note: Why does update require the respond_to block while show will handle /students/1.json or /students/1 without any logic at all?
format is a local variable that respond_to yields. When you do format.html {} you are actually registering a callback block for a format.
Rails goes through the registered formats and tries to find a compatible format to the MIME type in the request. If there is no handler it will raise an error.
This could be explained as something like using syntactic sugar on top of a case statement (the Ruby equivalent of a switch statement). But the analogy is not completely accurate since Rails does a bit of work in matching the request type.
Also the code inside your block is not executed when the format.html block is registered (as it would be if it was just a conditional statement) but rather when respond_to finishes or not at all if you are using for example E-Tag caching.
Why does update require the respond_to block while show will handle
/students/1.json or /students/1 without any logic at all?
Rails handles many actions by using a convention over configuration approach and guessing the intent of the action.
def PostsController < ApplicationController
def index
# rails auto-magically fills in the controller with something
# like this
#posts = Post.all
respond_to do |format|
format.html { render :index }
format.json { render json: #posts }
end
end
def show
# convention over configuration is awesome!
#post = Post.find(params[:id])
respond_to do |format|
format.html { render :show }
format.json { render json: #post }
end
end
def new
#post = Post.new
render :new
end
def edit
#post = Post.find(params[:id])
render :edit
end
end
Rails assumes that there is a resource with the same name as the controller and auto-magically fills in the controller action. It also assumes there is a view in app/views/posts/(:action).html.[erb|haml|slim|jbuilder]. This is known as implicit rendering.
The comments show roughly what action rails attempts.
It does not fill in actions which operate on data (create, update, destroy) since the actual implementation can vary greatly and it's hard to make useful guesses.
Well, it depends on the format of the request. If a request demands HTML from the server, format.html block will be executed, and in the same way, if a request demands JSON format, format.json will be executed.
Rails will automatically(read: magically) handle the if (format == html) part for you. All you have to do is fill in the blanks. Same way, you can write a block for XML starting with format.xml.
And for the side note, I think you have said it otherwise. update method doesn't require respond_to block, while show requires. And the reason is very simple: update method is there to update the Model, and then, redirect you to somewhere, while show will always return you something. In your case, /students/1 will return you the first student created in the database, and the response will be HTML, while /students/1.json will return you the same result, but response will be JSON this time.
Well you could very well replace 'format' with 'foo' or 'banana' or whatever you want. It is just the variable name in this case because the variable that is sent to your block by respond_to is passing along the format as requested by the incoming http request's Accept header.
Sometimes you'll see 422 "Unacceptable" errors in your logs because you are receiving a request with an Accept header that does not request a mime type your app knows about.
As it is, your callers should be using a browser or be a JSON consumer sending the proper headers to receive responses from the boilerplate.

How to send http-status using JBuilder Gem

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

Resources