I've added a method inside of an existing Rails controller (reports_controller) to handle a specific action that is beyond the basic scope of REST. Let's call that action 'detail':
def detail
#report = Report.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #report }
end
end
I added the appropriate layout page (detail.html.erb) and routing to make sure that I can access the page from anywhere. This is how my route looks like:
map.connect "reports/:action", :controller => 'reports', :action => /[a-z]+/i
Right now I'm able to access any of the detail pages. An example page would look like this: http://127.0.0.1:3000/reports/detail/8
Now, I'm trying to create a link from the main Report index page to the detail pages, but when using the code below:
<%= link_to "Details", {:controller => "reports", :action => "detail", :id => #report }, {:title => "see details for this report"} %>
The link that is created does not include the report's ID on it an looks like this:
http://127.0.0.1:3000/reports/detail
Any idea of what's wrong with what I'm doing?
Thanks!
I think you might be looking for :member:
map.resources :reports, :member => { :detail => :get }
Using link_to:
link_to "Detail", detail_report_path(#report)
Syntax error, maybe?
Try no comma right after #report
Related
I have a Rails 3.0 application which features a simple voting mechanism that adds a count to a number of down votes. I've set up a PUT route in routes.rb and created a method in my controller to handle it. The down vote link itself renders properly, but on clicking it gives me a Routing Error No route matches "/venues/18/down_vote" error.
Here's my controller code:
def down_vote
#venue = Venue.find(params[:id])
respond_to do |format|
if #venue.update_attribute(:mon_closed_accuracy_downvotes => #venue.mon_closed_accuracy_downvotes + 1)
format.html { redirect_to(:back, :flash => { :success => "Shenanigans were successfully called on #{ #venue.name }'s closing time." }) }
format.xml { head :ok }
else
format.html { render :action => 'show' }
format.xml { render :xml => #venue.errors, :status => :unprocessable_entity }
end
end
end
Here's my route code for the down_vote route:
resources :venues do
put 'down_vote', :on => :member
end
View code (inside a partial for a list of venues):
<%= link_to 'Incorrect?', down_vote_venue_path(venue), :method => :put %>
Again, the view itself renders fine, and the link renders as expected:
Incorrect?
Any ideas?
Sounds like you've got a problem with your javascript. Are you including the proper files? What does your layout look like?
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.
def show
render :text => params.inspect
end
What is render :text =>?
What is render, :text, and the =>?
Are they standard ruby?
The syntax you see used in that code snippet is not limited to render(), but it is common with many other Ruby on Rail methods.
The method is accepting a hash map, using a simplified syntax.
The code is equivalent to
def show
render({:text => params.inspect})
end
Other code snippets that include the same syntax are:
def sign
Entry.create(params[:entry])
redirect_to :action => "index"
end
url_for :controller => 'posts', :action => 'recent'
url_for :controller => 'posts', :action => 'index'
url_for :controller => 'posts', :action => 'index', :port=>'8033'
url_for :controller => 'posts', :action => 'show', :id => 10
url_for :controller => 'posts', :user => 'd', :password => '123'
def show
#article = Article.find(params[:id])
fresh_when :etag => #article, :last_modified => #article.created_at.utc, :public => true
end
render is rails API. See doc. For everything else, let me recommend you something and you will understand.
the syntax you have posted is a prettier way of writing
render({:text => "hello world"})
basically, you are calling a method, passing in a Hash object (which is a collection of key value pairs). The hash contains 1 pair, with a key of :text (: indicating it is a symbol called text), the value being a string of "hello world"
I think you should really be reading the ruby getting started guides before digging too deep in to rails.
The render :text idiom is for rendering text directly to the response, without any view. It's used here for debugging purposes, it's dumping the contents of the params hash to the response page without going through the page view.
render :text => "hello world!"
Renders the clear text "hello world" with status code 200
This is what the :text => ... means
refer http://api.rubyonrails.org/classes/ActionController/Base.html
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
I'm trying to write my first custom controller action and am running into a NoMethodError.
The scenario: PostsController, with the standard 7 actions scaffolding generates.
I want the index page to show a single post instead of displaying a list of them. Each post has a "visible" checkbox. I created a named_scope called visible in the Post model with:
named_scope :visible, :conditions => ["visible =?", true]
and applied it along with .last to the index action in my controller:
def index
#post = Post.visible.last
(format stuff)
end
Edited my index view to remove the iterating over an array part so it just shows #post.title, etc. So now my index just displays the last record in the Posts table with True on visible.
I then want to create an all posts archive page, that shows all of them regardless of visibility, so I created a new controller action:
(edited for clarity)
def archives
render :layout => 'posts'
#posts = Post.find(:all)
respond_to do |format|
format.html
format.xml { render :xml => #posts }
end
end
created a new named route:
map.archives 'posts/archives', :controller => 'posts', :action => 'archives'
and a view named archives.html.erb underneath views/posts.
Archives.html.erb looks exactly like the standard index.html.erb template that scaffolding creates, with a
<% for post in #posts %>
<tr>
<td><%=h post.title %></td>
...
</tr>
<% end %>
When I view this page in my browser, I get an error of
NoMethodError in Posts#archives
You have a nil object when you didn't expect it, you might have expected an isntance of Array, the error occurred while evaluating nil.each,
pointing to the line in the view (app/views/posts/archives.html.erb) that says
<% for post in #posts %>
so my understanding is that the data from the instance variable in the controller action archives isn't passing into the view, so instead of an array of posts, I've got nil. I'm looking for a reason why, and what it is I'm doing wrong.
(Of course, if I put
<% #posts = Post.find(:all) %>
before the
<% for post in #posts %>
in the view, then it works just fine, but I know this is not what I should do.
All help, links to tutorials to straighten me out, etc is much appreciated.
(I'm doubtful it matters with something this simple, but I'm using Rails 2.2.2 and Ruby 1.8.6.)
This is correct:
respond_to do |format|
format.html { render :layout => 'posts' }
format.xml { render :xml => #posts }
end
end
#posts was not recognized by the view because it was instantiated after you rendered the view.
'render' should be the last executed line in an action.