I've just added a contact form to my Rails application so that site visitors can send me a message. The application has a Message resource and I've defined this custom route to make the URL nicer and more obvious:
map.contact '/contact', :controller => 'messages', :action => 'new'
How can I keep the URL as /contact when the model fails validation? At the moment the URL changes to /messages upon validation failure.
This is the create method in my messages_controller:
def create
#message = Message.new(params[:message])
if #message.save
flash[:notice] = 'Thanks for your message etc...'
redirect_to contact_path
else
render 'new', :layout => 'contact'
end
end
Thanks in advance.
One solution would be to make two conditional routes with the following code:
map.contact 'contact', :controller => 'messages', :action => 'new', :conditions => { :method => :get }
map.connect 'contact', :controller => 'messages', :action => 'create', :conditions => { :method => :post } # Notice we are using 'connect' here, not 'contact'! See bottom of answer for explanation
This will make all get request (direct requests etc.) use the 'new' action, and the post request the 'create' action. (There are two other types of requests: put and delete, but these are irrelevant here.)
Now, in the form where you are creating the message object change
<%= form_for #message do |f| %>
to
<%= form_for #message, :url => contact_url do |f| %>
(The form helper will automatically choose the post request type, because that is default when creating new objects.)
Should solve your troubles.
(This also won't cause the addressbar to flicker the other address. It never uses another address.)
.
Explanation why using connect is not a problem here
The map.name_of_route references JUST THE PATH. Therefore you don't need a new named route for the second route. You can use the original one, because the paths are the same. All the other options are used only when a new request reaches rails and it needs to know where to send it.
.
EDIT
If you think the extra routes make a bit of a mess (especially when you use it more often) you could create a special method to create them. This method isn't very beautiful (terrible variable names), but it should do the job.
def map.connect_different_actions_to_same_path(path, controller, request_types_with_actions) # Should really change the name...
first = true # There first route should be a named route
request_types_with_actions.each do |request, action|
route_name = first ? path : 'connect'
eval("map.#{route_name} '#{path}', :controller => '#{controller}', :action => '#{action}', :conditions => { :method => :#{request.to_s} }")
first = false
end
end
And then use it like this
map.connect_different_actions_to_same_path('contact', 'messages', {:get => 'new', :post => 'create'})
I prefer the original method though...
I just came up with a second solution, guided by Omar's comments on my first one.
If you write this as your resources route
map.resources :messages, :as => 'contact'
This gives (amongst others) the following routes
/contact # + GET = controller:messages action:index
/contact # + POST = controller:messages action:create
So when you move your 'new' action code into your 'index' action, you will have the same result. No flicker and an easier to read routes file. However, your controller will make no more sense.
I, however, think it is a worse solution because you'll soon forget why you put your 'new' code into the index action.
Btw. If you want to keep a kind of index action, you could do this
map.resources :messages, :as => 'contact', :collection => { :manage => :get }
This will give you the following route
manage_messages_path # = /contact/manage controller:messages action:manage
You could then move your index action code into the manage action.
I suspect you are posting to '/messages' from the form which creates the message which explains why you see that in your URL.
Any reason why this won't work:
def create
#message = Message.new(params[:message])
if #message.save
flash[:notice] = 'Thanks for your message etc...'
redirect_to contact_path
else
flash[:notice] = 'Sorry there was a problem with your message'
redirect_to contact_path
end
end
Not to my knowledge, no. Since im assuming you want to render so that you keep the #message object as is with the errors attached.
There is a horrible solution that I have which will let you do it, but, its so horrible, I wouldn't recommend it:
before_filter :find_message_in_session, :only => [:new]
def new
#message ||= Message.new
end
def create
#message = Message.new(params[:message])
if #message.save
flash[:notice] = 'Thanks for your message etc...'
redirect_to contact_path
else
flash[:notice] = 'Sorry there was a problem with your message'
store_message_in_session
redirect_to contact_path
end
end
private
def find_message_in_session
#message = session[:message]; session[:message] = nil
end
def store_message_in_session
session[:message] = #message
end
Related
As I have written in the title I get the following error when I try to click on my games page.
This is what I have in my games_controller:
def email
respond_to do |format|
user = #game.user
email = user.email
g = GameTrade.game_interest(user)
g.deliver
format.html { redirect_to root_url }
format.json { render json: #game }
end
end
and this is what I have in the show.html.erb
<%= button_to "Send Email", :action => 'email'%>
in my routes page I do have the line resources :games
"email" is not a standard route for a resource, so you'll need to add a route for it. Ideally this would be a POST request, since following that link (button) causes changes to occur. Finally, you should really stick to using the url helpers rather than specifying actions as they make your code a little brittle.
Try something like this in your routes.rb:
resources :games do
post 'email', :on => :member
end
Then in your view, generate your button like so:
<%= button_to "Send Email", email_game_path(#game) %>
You'll then need to find the game in your email action in the usual way before you can do anything with it:
#game = Game.find(params[:id])
I have a problem with passing params. in show view i have
=link_to "Send message", {:controller => "users", :action =>
"send_message", :user_id => #user.id}
and my users_controller have method
def send_message
#user = User.find(params[:user_id])
#message=Message.new
redirect_to send_message_path
end
and my show method
def show
#user = User.find(params[:id])
#post=Post.new
#posts=#user.posts.reverse.paginate(:page => params[:page],:per_page => 10)
end
but i have and error after clicking on link
Couldn't find User without an ID
in that line, in send_message method
#user = User.find(params[:user_id])
what i am doing wrong? i read a lot examples about this , but it doesnt work(
Thanks in advance!!!
=link_to "Send message", [:send_message, #user]
and in your routes:
resources :users do
post :send_message, :on => :collection
end
What happens if you try to change :user_id to :id, like this
=link_to "Send message", {:controller => "users", :action => "send_message", :id => #user.id}
def send_message
#user = User.find(params[:id])
#message=Message.new
redirect_to send_message_path
end
If that works it's because your route isn't set up to use a :user_id param. If it doesn't it would help to know more about your route.
In your routes you should write:
resources :users do
post :send_message, :on => :member
end
And in your view you write:
= link_to "Send Message", send_message_user_path(#user)
This will use id instead of user_id.
So in your controller you need to use params[:id] as well.
Imho this is cleaner than #fl00r solution, because it is a member method, not a collection.
But I am a bit confused as to why you would have that method inside the controller at all.
Because the only thing it does is redirect to the send_message_path anyway. The instance variables you set are all lost then. So instead in your view write this:
= link_to 'Send message', send_message_path(:user_id => #user.id)
And this will send you to the correct controller straightaway (where you will have to retrieve the user).
Also your show method could use some work: you should define a has_many :posts in your User model.
I have read up on other people's posts about this and I still can't get my head wrapped around the problem I am having. So I thought I would ask.
I have a form that gets for uploading an avatar.
This form is displayed from :controller => 'board', :action => 'show'
<% form_tag("avatar/upload", :multipart => true ) do %>
<%= error_messages_for :avatar %>
...
This works great. Problem is that I can't get the error messages to display.
The upload is handled by :controller => 'avatar', :action => 'upload'
if params_posted?(:avatar)
image = get_image(params)
#board = Board.find(session[:board_id])
#avatar = Avatar.new(#board.id, image)
if #avatar.save
# ???
end
end
Now this is the part that I have trouble with. I know I can't do a redirect_to or I will lose the error_messages_for #avatar and thus get no error messages but doing a render is a problem because I have some routes.
In my routes.rb I have the following:
map.connect 'board/celebrating/:id/:name', :controller => 'board', :action => 'show'
So what I want to know is how to display the board again located at :controller => 'board', :action => 'show' and display the error messages for #avatar?
Sorry if this seems trivial. To me its been a struggle.
Thank you in advance.
Mitchell
You can do it by rendering a template instead of redirecting or rendering an action. For example, if you want to render the boards/show.html.erb on a failure, you would do the following:
if params_posted?(:avatar)
image = get_image(params)
#board = Board.find(session[:board_id])
#avatar = Avatar.new(#board.id, image)
if #avatar.save
...
else
render :template => 'boards/show'
end
end
Keep in mind that the BoardsController#show action will not be run, so your flash messages and instance variables (#avatar, #board) will be preserved.
I made these changes appropriately:
Now in 'app/controllers/users_controller.rb' I have this code:
def reset
respond_to do |format|
format.html # reset.html.erb
end
end
def send_reset
respond_to do |format|
if #user.errors.empty?
...
else
format.html { render :action => 'reset' }
end
end
end
Now in 'config/routes.rb' I have this code:
resources :users do
collection do
get 'reset'
post 'reset'
get 'send_reset'
post 'send_reset'
end
end
If I submit the form ('app/views/users/reset.html.erb') related to changes
<%= form_tag( send_reset_users_path, :method => :post ) do %>
<%= text_field_tag :email %>
<%= submit_tag("Send") %>
<% end %>
the browser URL become '.../users/send_reset' but I want '.../users/reset' (I think the problem is around the 'format.html { render :action => 'reset' }' code that does not render correctly the action) or, maybe, the route.
In few words I aim to use the 'send_reset' action like I use, for example, the 'update', 'create' or 'destroy' actions (from scaffold), in my case that is without creating the 'app/views/users/send_reset.html.erb' file but just calling the action method to handle my issue. How can I make this?
The default Rails URL scheme is
:host/:controller/:action/:id
Rename your controller action from
def send_reset
end
to
def reset
end
and rename the views and routes to match this change.
However you are already using reset for get and send_reset for Post but you want them to be the same just do different things if you ask for the page or send a form POST.
def reset
case request.method
when :post
# send them an email
# redirect_to :somewhere_else
else # :get, :put, :delete
# render the view with the recovery form
end
end
I am rendering a new action but somehow getting the "index" URL. To be more specific, my create action looks like this:
class ListingsController < ApplicationController
def create
#listing = Listing.new(params[:listing])
#listing.user = #current_user
if #listing.save
redirect_to #listing
else
flash[:error] = "There were errors"
render :action => "new"
end
end
end
When there are errors, I get the "new" action but my URL is the index URL - http://domain.com/listings
Anyone know why this would happen? My routes file is fairly standard:
map.connect 'listings/send_message', :controller => 'listings', :action => 'send_message'
map.resources :listings
map.root :controller => "listings"
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
when you render you just get the content that will be returned to the browser as the response body. On Rendering your url is not get changed.
Best example of that create a scaffold application.so when you submit the form on the new and error occurs your 'new.html.erb' is displayed but your url shows domain_name/controller_name/create
Hope that helps :)