Control redirect after create in certain circumstances - ruby-on-rails

I have a rails app that has multiple ways of creating tasks
a) A normal task new screen with lots of options
b) A quick task create screen which has the minimum actions and only has the minimum fields to enable quick creation. And a list of the actions
After the create succeeds I want
a) to redirect to the standard show form
b) to redirect back to the quick edit page with a blank quick creation box and the new task in the list.
If create fails on validation I want
a) to redirect to the edit screen with the fields highlighted
b) to redirect to the quick create screen with the fields highlighted and the data still there.
I've tried editing the create respond_to if.save? but that seems to apply to everything in both cases.
There's a slight complication in that I create tasks either generically (no client selected) or as a nested route under client where the client is autoselected, and ideally I'd like to go back to that nested route location.
I'd like to control that respond to by using an if parameter that recognises where the call is coming from
if from quick_create
if #task.save?
redirect
else
reload table and clear
if from new
if task.save?
Any ideas?
Adding current controller code and routes:
routes.rb
get 'tasks/quick_create' => 'tasks#quick_create'
-----
resources :clients do
match 'tasks/quick_create' => 'tasks#quick_create'
----
tasks_controller.rb
def create
#task = Task.new(params[:task])
#task.practice_id = current_user.practice_id
unless #task.recurring.present?
#task.build_recurring
end
#task.create_recurring_tasks
if params[:batch_task] == "Create Task"
#client = Client.find(params[:client_id])
#task.build_batch_task(#client)
end
respond_to do |format|
if #task.save
format.html { redirect_to #task, notice: 'Task was successfully created.' }
format.json { render json: #task, status: :created, location: #task }
else
format.html { render action: "new" }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end
end
def quick_create
if params[:client_id]
#client = Client.find(params[:client_id]).tasks
#tasks = #client.accessible_by(current_ability, :read).order(:due_date)
else
#tasks = Task.accessible_by(current_ability, :read).order(:due_date)
end
#task = Task.new
#task.status = "Not Complete"
##task.task_files.build
#task.build_recurring
respond_to do |format|
format.html # index.html.erb
format.json { render json: #tasks }
end
end
Ibasically want to control the respond do bit with something like
if request.path.include? "quick_create"
And be able to flag up the errors on quick create. There appears to be two things.
1. The request.path if statement doesn't work
2. The #task when attempting to feed back to the quick_create page hits an error (since it expects both #task and #tasks, I think).
Anyway....

I imagine You may have two options -
specify some custom parameter inside URL and use it to decide where redirect to
or use different actions as already suggested.
If You are having problems with two different create actions, post current controller code and Your routes otherwise we can't help You.

Related

Rails 5, rendering the same view in a consecutive action doesn't refresh the browser

Quick question, I've been trying for the last couple hours to discern what is causing the following behavior but it's just beyond my grasp.
I have this two actions on my 'UsersController':
def new
#user = User.new
end
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
flash[:success] = 'Wellcome, %s! You have successfully
registered.' % [#user.name]
format.html { redirect_to login_path }
format.json { render :show, status: :created, location: #user }
else
flash.now[:error] = 'Hmm... There seems to be some errors.'
format.html { render :new }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
Basically, we render a clean 'new' view, try to register a new user, and if the creation of a user fails, the application should flash a message for the current action (the create action), and render the 'new' view, updating the previous one with the flash information and the errors of the #user variable.
The problem is that, although the server processes the response just fine, the browser does not update the page, never re-renders the page, it keeps the stale 'new' view. I've looked the response with chrome's web tools and it bears the updated view, but for some reason the browser just won't render it.
I think it has something to do with caching, but really I'm out of my element here. If instead of rendering I just redirect to the new action, the flash works fine (removing the .now(), that is), but this way I lose the #user, which I would like to keep with it's full functionality.
Any ideas why this behaves like this, or at least how to solve it?
If you redirect_to the new action, when the user submits, it will still post to the create action and the user_params would still take effect. Simplying rendering :new on the already new page will not perform a fresh request/response. To initiate a fresh request you will have to use redirect_to.

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.

Form submission help needed

I'm developing a small project using Ruby on Rails where basically student can sign up for a study room. I have one modification that I'm trying to make that I can't figure out yet.
On default when I create new submission, it creates it and goes to the view screen that basically shows all the info from the submission. Here is the controller for the form:
# POST /students
# POST /students.json
def create
#student = Student.new(params[:student])
respond_to do |format|
if #student.save
format.html { redirect_to #student, notice: 'Student was successfully created.' }
format.json { render json: #student, status: :created, location: #student }
else
format.html { render action: "new" }
format.json { render json: #student.errors, status: :unprocessable_entity }
end
end
end
What I'm trying to do is when the user clicks the submit button it doesn't automatically save it to the DB, but instead it goes to the next screen where the user can review their submission and confirm the submission by clicking the button. Once they click the confirm button it should save it to the DB and show the 'Student was successfully created.' notice and take them back to the form screen.
Here is the controller for the show (which is the next page that gets displayed):
# GET /students/1
# GET /students/1.json
def show
#student = Student.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #student }
end
end
Some key points:
Don't use #show to display the preview. #show is supposed to display normal persist objects, while your new object is not persist yet. You also need extra element on preview such as "confirm" link/button.
It's better to use a separate #preview action, which accepts form submit from #new, and will send confirmed object to #create.
You need a vehicle to pass object in #preview, both force(to #create) and back(to #new if not satisfied). Though I don't like to use session, it seems there are not too much choices.
The example code:
def new
#student = session[:student].blank? ? Student.new : session[:student]
# ...
render
end
def preview
#student = Student.new(params[:student])
session[:student] = #student
# ....
render
end
def create
#student = session[:student]
session[:student].clear
# ...
render
end
Add
More about #3. In #create you need params to build a new object with full data to save. However in this case the form is not submitted to #create but #preview instead. So how do you get the data? There are three ways:
a. Plain display(just like #show). In #preview, build a form with hidden fields filled with data sent by #new.
b. Plain display. Use session to pass data instead of form. Much simple than #a.
c. Show both plain text and a form in #preview. User can preview the submission and edit it right in #preview.
According to your need, #b and #c are all good IMO.

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.

Curl POST to Rails application when controller looks like this

I'm trying to curl POST my Rails application in order to create a new Entry object. The problem is my entries_controller Create action looks like this:
def create
#user = current_user
#entry = #user.entries.build(params[:entry])
respond_to do |format|
if #entry.save
format.html { redirect_to landing_page_url, notice: 'Entry was successfully created.' }
format.json { render json: #entry, status: :created, location: #entry }
else
format.html { render action: "new" }
format.json { render json: #entry.errors, status: :unprocessable_entity }
end
end
end
Calling #user.entries.build just returns an exception because current_user doesn't exist. The thing is the Create action works well when I use the browser to create an Entry (as I login and create the current_user variable) but I do not know if it's possible to curl POST and create an Entry without changing the controller logic. And if it's not possible, could someone help reach the right direction towards building the controller logic (compatible with curl POST)?
I'm sorry if this is a dumb question, I'm fairly new to all this.
PS: I'm using Rails 3.2.3, if that's of any help.
Not sure how you create current_user (what it's based off), but this must exist for your method to work.
If you can pass a param to your action that specifies the user and set current_user as current_user ||= params[:user]... beware of the security implications of this.
You really should have a before_filter on your action to set current_user (via login, I'm presuming).

Resources