Render different action for ajax vs html request in rails 3 - ruby-on-rails

In rails three I have the following code for my destroy action in a photos controller
def destroy
#photo = Photo.find(params[:id])
if #photo.destroy
flash[:notice] = t('photo.deleted')
respond_to do |format|
if request.xhr?
format.js
else
format.html {redirect_to photos_path}
end
end
else
flash[:alert] = t('.photo.error_deleting')
if request.xhr?
redirect_to(photos_url)
else
redirect_to(photo_path #photo)
end
end
end
The goal is essentially to redirect to the index page if this is called from a standard link and render destroy.js if called from a remote link.
This works but I was wondering if there is a cleaner way of doing this in rails 3. Possibly using the respond_with operator?
Thanks

This should work for you:
respond_to :html, :js
def destroy
#photo = Photo.find(params[:id])
if #photo.destroy
flash[:notice] = t('photo.deleted')
else
flash[:alert] = t('.photo.error_deleting')
end
respond_with(#photo)
end
There is a good blog post about it here:
http://ryandaigle.com/articles/2009/8/10/what-s-new-in-edge-rails-default-restful-rendering
Here's a quote from the post about the logic:
If the :html format was requested:
If it was a GET request, invoke render (which will display the view
template for the current action)
If it was a POST request and the resource has validation errors, render
:new (so the user can fix their
errors)
If it was a PUT request and the resource has validation errors, render
:edit (so the user can fix their
errors)
Else, redirect to the resource location (i.e. user_url)
If another format was requested, (i.e. :xml or :json)
If it was a GET request, invoke the :to_format method on the resource and
send that back
If the resource has validation errors, send back the errors in the
requested format with the
:unprocessable_entity status code
If it was a POST request, invoke the :to_format method on the resource and
send that back with the :created
status and the :location of the new
created resource
Else, send back the :ok response with no body
A little more on the to_format part from the documentation:
First we try to render a template, if
the template is not available, we
verify if the resource responds to
:to_format and display it.
There is also a Railscast about it:
http://railscasts.com/episodes/224-controllers-in-rails-3

Related

Keep model with errors when redirecting to edit

When there was an error on updating my model, I was rendering :edit, but this was stripping the /edit from my url because #update is the same as #show with a different request method. To solve this I tried following the advice given here, but this caused me to get an ActionDispatch::Cookies::CookieOverflow error when I try to submit an invalid form. How should I correctly re render the edit page, while keeping both the /edit url and the flash messages? Is it possible to check for validity and show the errors without making a call to update?
Original code:
def edit
end
def update
respond_to do |format|
format.html do
if #model.update(model_params)
redirect_to home_base_url_or_default(model_url(#model)), notice: "Successfully updated."
else
render :edit
end
end
end
end
Failing code:
def edit
if flash[:model]
#model = flash[:model]
end
end
def update
respond_to do |format|
format.html do
if #model.update(model_params)
redirect_to home_base_url_or_default(model_url(#model)), notice: "Successfully updated."
else
flash[:model] = #model
redirect_to :action => :edit
end
end
end
end
Rather than doing a redirect, in this case the problem was solved by doing a render, then controlling the view by setting an instance var in the controller saying if it is the edit page or not. Also by using the update class in the CSS. However, this still has the the url for the show page, but at least the layout is correct.
One way to do it would be to allow the edit action to accept the POST method as well. Use request.method to check whether it is a POST or GET, then perform your render or redirect accordingly.

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.

What does respond_to do when called without a block?

I understand how respond_to works when it's called with something like this:
def index
#users = User.all
respond_to do |format|
format.html
format.json { render json: #users }
end
end
But I've seen some apps which pass respond_to a list of symbols, outside of the controller methods, e.g.:
class UsersController < ApplicationController
respond_to :html, :json
def index
# blah blah bah
end
end
What does this do? I've been playing around with it in one of my controllers and I can't figure out what difference it makes.
For a given controller action, #respond_with generates an appropriate response based on the mime-type requested by the client.
If the method is called with just a resource, as in this example -
class PeopleController < ApplicationController
respond_to :html, :xml, :json
def index
#people = Person.all
respond_with #people
end
end
then the mime-type of the response is typically selected based on the request's Accept header and the set of available formats declared by previous calls to the controller's class method respond_to. Alternatively the mime-type can be selected by explicitly setting request.format in the controller.
If an acceptable format is not identified, the application returns a '406 - not acceptable' status. Otherwise, the default response is to render a template named after the current action and the selected format, e.g. index.html.erb. If no template is available, the behavior depends on the selected format:
for an html response - if the request method is get, an exception is raised but for other requests such as post the response depends on whether the resource has any validation errors (i.e. assuming that an attempt has been made to save the resource, e.g. by a create action) -
If there are no errors, i.e. the resource was saved successfully, the response redirect's to the resource i.e. its show action.
If there are validation errors, the response renders a default action, which is :new for a post request or :edit for patch or put.
Thus an example like this -
respond_to :html, :xml
def create
#user = User.new(params[:user])
flash[:notice] = 'User was successfully created.' if #user.save
respond_with(#user)
end
is equivalent, in the absence of create.html.erb, to -
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
flash[:notice] = 'User was successfully created.'
format.html { redirect_to(#user) }
format.xml { render xml: #user }
else
format.html { render action: "new" }
format.xml { render xml: #user }
end
end
end
for a javascript request - if the template isn't found, an exception is raised.
for other requests - i.e. data formats such as xml, json, csv etc, if the resource passed to respond_with responds to to_, the method attempts to render the resource in the requested format directly, e.g. for an xml request, the response is equivalent to calling render xml: resource.

Rails redirect_to in controller to correct page

I have several models with has_many :attachments.
I'm trying to redirect back to the Note view after the Note is created.
This is the attachments controller code I'm trying. The #note tells me that this attachment is related to that Note.
# GET /attachments/new
# GET /attachments/new.json
def new
#attachment = Attachment.new
#comment = params[:comment_id]
#note = params[:note_id]
respond_to do |format|
format.html # new.html.erb
format.json { render json: #attachment }
end
end
# POST /attachments
# POST /attachments.json
def create
#attachment = Attachment.new(params[:attachment])
respond_to do |format|
if #attachment.save
if #note != nil
format.html { redirect_to note_path(#note), notice: 'Attachment was successfully created.' }
else
format.html { redirect_to attachments_path, notice: 'Attachment was successfully created.' }
end
But, #note is nil by the time the create code happens.
Thanks for the help!
As a rule, you probably won't see "new" and "create" blocks executed in the same context. That's a bit of a mouthful, so lets be a bit more specific: the variables you declare in "new" won't still exist when "create" is called. So, any variables you want to use in "create" must be declared there as well.
One thing you can do (depending on the code) is share a block between different controller methods that initialized these variables for you. For example:
before_filter :initialize_vars, only: [:new, :create]
...
def initialize_vars
#note = params[:note_id]
end
The "before_filter" will execute the "initialize_vars" method before any new request is sent to the "new" or "create" methods.
More generally, this relates to a pretty important Rails concept (and server-side web engineering in general) - that there is very little "state" within the server. The server takes a request, processes it, and forgets about it. Everything that's needs to be remembered must be stored in the server, or somehow communicated by the request the user sends.

How do I define a custom URL for a form confirmation page?

I am creating a basic 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 successful submit of the form, I would like to redirect to a custom '/thanks' page in which I thank users for their interest in the product (and also encourage them to complete a short survey.)
Currently, successful submits are displayed at "/invites/:id/" eg "invites/3" which I do not want since it exposes the number of invites that have been submitted. I would like to instead redirect all successful submits to a "/thanks" page.
I have attempted to research "rails custom URLs" but have not been able to find anything that works. The closest I was able to find was this Stackoverflow post on how to redirect with custom routes but did not fully understand the solution being recommended. I have also tried reading the Rails Guide on Routes but am new to this and did not see anything that I understood to allow for creating a custom URL.
I have placed my thanks message which I would like displayed on successful form submit in "views/invites/show.html.haml"
My Routes file
resources :invites
root :to => 'invites#new'
I tried inserting in routes.rb:
post "/:thanks" => "invites#show", :as => :thanks
But I don't know if this would work or how I would tell the controller to redirect to :thanks
My controller (basically vanilla rails, only relevant actions included here):
def show
#invite = Invite.find(params[:id])
show_path = "/thanks"
respond_to do |format|
format.html # show.html.erb
format.json { render json: #invite }
end
end
# GET /invites/new
# GET /invites/new.json
def new
#invite = Invite.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #invite }
end
end
# POST /invites
# POST /invites.json
def create
#invite = Invite.new(params[:invite])
respond_to do |format|
if #invite.save
format.html { redirect_to #invite }
#format.js { render :action => 'create_success' }
format.json { render json: #invite, status: :created, location: #invite }
else
format.html { render action: "new" }
#format.js { render :action => 'create_fail' }
format.json { render json: #invite.errors, status: :unprocessable_entity }
end
end
end
It would seem as if creating a standard URL for displaying a confirmation would be relatively straightforward. Any advice on how to achieve this would be appreciated.
I guess you want to redirect after your create action, which is executed when the form is submitted.
Just add redirect_to in the following way:
def create
#invite = Invite.new(params[:invite])
if #invite.save
...
redirect_to '/thanks'
else
...
redirect_to new_invite_path # if you want to return to the form submission page on error
end
end
I omitted some of the code for brevity.
In your routes add:
get '/thanks', to: "invites#thanks"
Add the thanks action to your invites controller:
def thanks
# something here if needed
end
And create a thanks.html.erb page in app/views/invites.
I would do get "/thanks" => "invites#thanks" in routes.rb and then add this in your controller:
def thanks
end
Then add a file app/views/invites/thanks.html.erb with your thank-you content.
You could create a route like this:
resources :invites do
collection do
get 'thanks'
end
end
This will also create a path helper called thanks_invites_path.
It will be at the invites/thanks path, but if you want it to be on/thanks, you could just do as Jason mentioned:
get "/thanks" => "invites#thanks", :as => :thanks
The as part will generate a helper to access that page: thanks_path.
You would need a extra action in the controller called thanks, and put whatever info you need inside, and also you will need a additional view called thanks.html.erb
Since you want everybody to go to that page after a successful submit, in your create action you would have:
format.html { redirect_to thanks_invites_path} (or thanks_path), what ever you choose, when you name the route you can check it with rake routes if it's okay, and whatever rake routes says, just add _path at the end.

Resources