On an unsuccessful save, I would like to redirect to the previous view but with the error message.
redirect_to user_path(#user_id), errors: #user.errors
but in the view, when I check for errors I get an undefined variable errors.
I am not using the same controller new and create, so I can't have #user.errors.any in new.html.erb. I have two different controllers, one in which form is there, and another controller which will take care of create, if the create is not happening I need to redirect to the previous controller.
You may need to use render instead of redirect_to.
Something like this:
# controller_1
def step_1
#user = User.new
#user.do_something
...
end
# controller_2
def step_2
if #user.save?
# redirect to another...
else
render 'controller_1/step_1`
end
end
Then on view step_1.html.erb, you can print out errors of #user with #user.errors.
You have to pass the parameters inside the redirect_to helper like below,
redirect_to user_path(id: #user_id, error: #user.errors.messages)
Please check the rake routes and pass the appropriate key for id, whether it's :id, or :user_id
Related
I'm trying to implement a form with the following behavior:
1) Input some semantic data about a user (i.e. username).
2) Do a User.find_by(:username = username).
3) If such a user exists, direct to show page for that user.
4) If such a user does not exist, create a new user with the provided username, then redirect to the show page for that user.
This should be simple but I cannot figure out how to format the form_for helper and my show and create actions in my user_controller to implement this behavior.
I currently have:
form_with :url => 'users/:id', :method => :get do
...
end
because I'm ultimately trying to invoke the "show" method of the controller. However, my form does not take in a user's id as a parameter, and when the user does not yet exist there is no :id parameter to access at the time of the form's submission.
How can I set up my form to redirect to show in each case, while still adhering to the logic explained above?
You can do something like this in your User's Controller create action
def create
usr_name = params[:username]
#user = User.where(username: usr_name).first_or_initialize
if #user.persisted?
redirect_to user_path(#user) # or whatever your user show path is
elsif #user.save
redirect_to user_path(#user)
else
render :new
end
end
You would just need to make sure that you are validating the uniqueness of usernames.
Also, first_or_initialize(and its counterpart first_or_create) can take in a block. So, you can assign other attributes to the new User like this...
User.where(username: usr_name).first_or_initialize do |usr|
usr.some_attribute = some_value
end
you can use find_or_initialize_by(unique_key) in your create method.unique_key can be any key which you are using to identify your user such as email,phone etc.
I'd like to call a controller's new action to create a new object using an existing one to set defaults. So far, what I've done to to try this:
link_to 'New' new_item_path(t)
In a Wice Grid table where t is an instance of item.
The route that gets generated is /item/new.10 where the 10 is the :id of the item that I'd like to use as a default. I can get at it through request.referrer, but I'm wondering if there is a better/cleaner way to do this?
If you're setting defaults on a new object, you should consider setting those defaults at the database level within a migration. To keep your system RESTful, you should keep the 'new' action url independent of the object you're grabbing defaults from i.e. just /item/new.
If you want to set defaults from the existing object without applying at the database level, you should consider creating an instance variable in your controller action, and using that in your view to set the default values in your form elements. Another option would be to edit the object in a before_create callback within your model and set the fields there. Changing your route to accommodate this idea would be bad design IMO.
Passing the id to your controller as a param:
link_to "New Item Path", new_item_path(:default_item_id => item.id)
in your controller
#default_item = Item.find(params[:default_item_id])
request.referrer? You should be able to get at the id in you controller through params
Not sure if it's an issue but there should be a comma in between 'New' and the path specification
link_to 'New', new_item_path(t)
then in the controller you can setup strong params if you want to be thorough
ItemsController < ApplicationController
def new
# new action
end
def create
item = Item.new(item_params)
if item.save
flash[:notice] = 'Successfully created your item.'
redirect_to item #if there's an item#show page
else
flash[:alert] = 'Error: item not created.'
render :new
end
end
private
def item_params
params.require(:item).permit(:attribute_1, :attribute_2, ...)
end
end
Hope this helps
I feel like this is simple but I'm banging my head against the wall. I'm trying to tell my Rails app that if one parameter is present (signature in this example) that I want to redirect home. Here's my code:
<%= if #pc.signature.present? %><% redirect_to "pages#home" %><%end%>
I keep running into a syntax error. This is in the edit.html.erb file by the way.
Perhaps in your controller you didn't define #pc? also, use path instead of 'pages#home'. it should look more like this:
def edit
#pc = Pc.find(params[:id]) #or whatever your logic is
redirect_to root_path if #pc.signature.present?
# otherwise 'edit' template will be rendered
end
You need to do that on your action controller, not in the view
def your_action
if #pc.signature.present?
redirect_to 'your_path_or_url'
end
end
When I look at examples of Rails controllers, I usually see something like this:
class WidgetController < ActionController::Base
def new
#widget = Widget.new
end
def create
#widget = Widget.new(params[:id])
if #widget.save
redirect_to #widget
else
render 'new'
end
end
end
This works, but there's a couple problems:
Routes
If I add widgets to my routes.rb file:
Example::Application.routes.draw do
resources :widgets
end
GET /widgets/new will route to new and POST /widgets will route to create.
If the user enters incorrect information on the new widget page and submits it, their browser will display a URL with /widgets, but the new template will be rendered. If the user bookmarks the page and returns later or refreshes the page, the index action will be called instead of the new action, which isn't what the user expects. If there's no index action or if the user doesn't have permission to view it, the response will be a 404.
Duplication of code
As a contrived example, let's say I had some tricky logic in my new method:
def new
#widget = Widget.new
do_something_tricky()
end
Using the current approach, I'd duplicate that logic in new and create. I could call new from create, but then I'd have to modify new to check if #widget is defined:
def new
#widget ||= Widget.new
do_something_tricky()
end
Plus, this feels wrong because it reduces the orthogonality of the controller actions.
What to do?
So what's the Rails way of resolving this problem? Should I redirect to new instead of rendering the new template? Should I call new inside of create? Should I just live with it? Is there a better way?
I don't think this is a problem in "the rails way" and there is no builtin functionality to allow this without getting your hands dirty. What does a user expects when bookmarking a form they just submitted and had errors? Users don't know better, and they shouldn't bookmark a failed form.
I think redirecting to new_widget_path is the cleanest solution. Yet, you should keep the errors and display them on the form. For this I recommend you keep the params in session (which I expect to be smaller than a serialized Widget object).
def new
#widget = widget_from_session || Widget.new
end
def widget_from_session
Widget.new(session.delete(:widget_params)) if session[:widget_params].present?
end
private :widget_from_session
# Before the redirect
session[:widget_params] = params
The code is self explanatory, Widget.new will only be called when widget_from_session returns nil, this is when session[:widget_params] is present. Calling delete on a hash will return de deleted value and delete it from the original hash.
UPDATE Option 2
What about submitting the form using ajax? Your controller could benefit from:
respond_to :html, :json
...
def create
#widget = Widget.new params[:widget]
#widget
respond_with #widget, location: nil
end
Based on the response code (which is set by Rails: 201 Created or 422 Unprocessable Entity), you could show the errors (available in the body of the response when validations fail) or redirect the user to #widget
This is how StackOverflow does it: https://stackoverflow.com/questions/ask. They submit the form asynchronously.
In general, I think the Rails way of solving the problem would be to put the tricky method onto the model or as a helper method, so the controller stays "thin" and you don't have to make sure to add custom behavior to both #new and #create.
EDIT: For further reading, I'd recommend the "Rails AntiPatterns" book, as they go through a lot of these common design issues and give potential solutions.
you put do_something_tricky() in its own method and call it inside the create action (but only when you're rendering the new template, ie when validation fails).
As for the bookmark issue, I don't know a good way to prevent that but to modify the routes and set the create action to the new action but using POST
get '/users/new' => 'users#new'
post '/users/new' => 'users#create'
UPDATE: using resources
resources :platos, except: :create do
post '/new' => 'plates#create', on: :collection, as: :create
end
then you can use create_platos_path in your forms
You don't need to write same function in two action , use before_filter instead.
If you want to have "widget_new_url" after incorrect submission then in your form add url of new widget path something like :url => widget_new_path .
Rails takes the url from Form .
I have this problem before, so I use edit action instead.
Here is my code.
Routes:
resources :wines do
collection do
get :create_wine, as: :create_wine
end
end
Controller:
def create_wine
#wine = Wine.find_uncomplete_or_create_without_validation(current_user)
redirect_to edit_wine_path(#wine)
end
def edit
#wine = Wine.find(params[:id])
end
def update
#wine = Wine.find(params[:id])
if #wine.update_attributes(params[:wine])
redirect_to #wine, notice: "#{#wine.name} updated"
else
render :edit
end
end
Model:
def self.find_uncomplete_or_create_without_validation(user)
wine = user.wines.uncomplete.first || self.create_without_validation(user)
end
def self.create_without_validation(user)
wine = user.wines.build
wine.save(validate: false)
wine
end
View:
= simple_form_for #wine, html: { class: 'form-horizontal' } do |f|
= f.input :complete, as: :hidden, input_html: { value: 'true' }
What I did is create a new action 'create_wine' with get action.
If user request 'create_wine', it will create a new wine without validation and redirect to edit action with a update form for attributes and a hidden field for compele .
If user has create before but gave up saving the wine it will return the last uncompleted wine.
Which means whether use save it or not, the url will be the same to /wines/:id.
Not really good for RESTful design, but solve my problem. If there is any better solution please let me know.
I am writing a Ruby on Rails application with a controller called "pages_controller" that is responsible for displaying pages to users. There are 3 different types of pages that can be displayed, and different things have to happen on the back end in each case, so I decided to break the functionality out into 3 methods within the controller. When the user requests a page, the "show" method is called, which figures out whether the page:
1. Belongs to the user
2. Belongs to another user, and can be viewed by the user requesting it
3. Belongs to another user, and cannot be viewed by the user requesting it (unauthorized)
The appropriate method is then called from there to display the page. The code looks something like this:
def show
if (something)
showMine
elsif (something else)
showAnother
else
showUnauthorized
end
end
def showUnauthorized
respond_to do |format|
format.html # showUnauthorized.html.erb
end
end
def showMine
respond_to do |format|
format.html # showMine.html.erb
end
end
def showAnother
respond_to do |format|
format.html # showAnother.html.erb
end
end
I am getting a template missing error because rails wants to render a view when "show" is called, but I do not want any views to be rendered when "show" is called. I simply want "show" to call the correct method from there, and the corresponding view for that method (showMine, showAnother, or showUnauthorized) to be rendered. How can I do this? Or am I going about this the wrong way entirely?
You need to declare these new actions that you have created in the routes file, as they don't belong to the RESTful routes.
I sugest to keep only the show action in your controller and create the IFs in the show view using the render method to include the partials(_showMine.html.erb, showAnother.html.erb, showUnauthorized)
example:
show view:
if (something)
<%= render 'showMine' %>
elsif (something else)
<%= render 'showAnother' %>
else
<%= render 'showUnauthorized' %>
end
I hope it helps...
I basically agree with Samy's comment, but here's some background:
The method that tells Rails what view to use is render. If there's no call to that method in your show method, Rails assumes you have a view called show.xxx.xxx, e.g. show.html.erb, that is supposed to be rendered. Note that it doesn't assume template will be prefixed with show because that's the name of the method. It assumes it will be show because that's the name of the action. The name of the action is passed to the controller as part of the request; it's not simply derived from the name of whatever method has a respond_to block in it.
All the respond_to blocks do is specify different view templates based on the MIME type of the request, but since you never call render, all of those extra methods are still trying to call the show view (show.html.erb in every case), because you never told Rails to render any other view, and the action name is show.
So, instead of the respond_to blocks, just call render [some_view] in each of your other methods.
This might not be the clearest answer, but I'd suggest also reading the following:
http://ryanbigg.com/2009/04/how-rails-works-2-mime-types-respond_to/
It describes what respond_to does, in particular how it keys off the action name to determine what view to render.