The nesting in /config/routes.rb has already been taken care of in the first tutorial:
map.resources :topics, :has_many => :replies
The first thing we are going to do is to create partials for /app/views/replies/new.html.erb and /app/views/replies/edit.html.erb. Create the file /app/views/replies/_reply.html.erb
#/app/views/replies/_reply.html.erb
<% form_for([@topic, @reply]) do |f| %>
<p>
<b>Subject</b><br />
<%= f.text_field :subject %>
</p>
<p>
<b>Body</b><br />
<%= f.text_area :body %>
</p>
<p>
<%= f.submit button_name %>
</p>
<% end %>
And now change edit and new to:
#/app/views/replies/edit.html.erb
<h1>Editing reply</h1>
<%= error_messages_for :reply %>
<%= render :partial => @reply, :locals => { :button_name => "Submit" } %>
<%= link_to 'Show', [@topic, @reply] %> |
<%= link_to 'Back', replies_path %>
#/app/views/replies/new.html.erb
<h1>New reply</h1>
<%= error_messages_for :reply %>
<%= render :partial => @reply, :locals => { :button_name => "Reply" } %>
<%= link_to 'Back', replies_path %>
Same stuff we did last time...
Now lets go to /app/controllers/replies_controller.rb
Add this at the top, to make sure a topic gets loaded everytime the replies controller is used:
before_filter :load_topic
def load_topic
@topic = Topic.find(params[:topic_id])
end
inside the new function change the line:
@reply = Reply.new
to this:
@reply = @topic.replies.build
Change the edit function:
#old edit function
def edit
@reply = Reply.find(params[:id])
end
#new edit function
def edit
@reply = @topic.replies.find(params[:id])
end
Change the create function:
#old create
def create
@reply = Reply.new(params[:reply])
respond_to do |format|
if @reply.save
flash[:notice] = 'Reply was successfully created.'
format.html { redirect_to(@reply) }
format.xml { render :xml => @reply, :status => :created, :location => @reply }
else
format.html { render :action => "new" }
format.xml { render :xml => @reply.errors, :status => :unprocessable_entity }
end
end
end
#new create
def create
@reply = @topic.replies.build(params[:reply])
respond_to do |format|
if @reply.save
flash[:notice] = 'Reply was successfully created.'
format.html { redirect_to([Forum.find(@topic.forum_id), @topic]) }
format.xml { render :xml => @reply, :status => :created, :location => @reply }
else
format.html { render :action => "new" }
format.xml { render :xml => @reply.errors, :status => :unprocessable_entity }
end
end
end
Notice the change in redirect_to...after a reply to a topic is submitted, I want to send the user back to the forum the topic was in, to get the forum, I had to use the find function and pass it the forum_id of the topic and the topic.
In the update and destroy functions:
#old update and destroy
def update
@reply = Reply.find(params[:id])
respond_to do |format|
if @reply.update_attributes(params[:reply])
flash[:notice] = 'Reply was successfully updated.'
format.html { redirect_to(@reply) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @reply.errors, :status => :unprocessable_entity }
end
end
end
def destroy
@reply = Reply.find(params[:id])
@reply.destroy
respond_to do |format|
format.html { redirect_to(replies_url) }
format.xml { head :ok }
end
end
#new update and destroy
def update
@reply = @topic.replies.find(params[:id])
respond_to do |format|
if @reply.update_attributes(params[:reply])
flash[:notice] = 'Reply was successfully updated.'
format.html { redirect_to(@topic) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @reply.errors, :status => :unprocessable_entity }
end
end
end
def destroy
@reply = @topic.replies.find(params[:id])
@reply.destroy
respond_to do |format|
format.html { redirect_to(@topic) }
format.xml { head :ok }
end
end
Now that we have all of that done...lets head over to /app/views/topics/show.html.erb so we can display our replies and the form for a new reply.
In addition to showing replies and a form for a new one, I changed the formatting a little, so here are the contents on show.html.erb
<p>
<h3>Forum: - <%=h @forum.name %></h3>
</p>
<p>
<b><%=h @topic.subject %></b>
</p>
<p>
<%=h @topic.body %>
</p>
<% unless @topic.replies.empty? %>
<% @topic.replies.each do |reply| %>
<p>
<b><%= reply.subject %></b>
</p>
<p>
<%=h reply.body %>
</p>
<% end %>
<% end %>
<h2>Reply</h2>
<%= render :partial => @reply = Reply.new, :locals => { :button_name => 'Reply' } %>
<%= link_to 'Edit', edit_forum_topic_path(@forum, @topic) %> |
<%= link_to 'Back', topics_path %>
Since this is the same stuff we were doing in the first tutorial, I won't bother explaining any of it...you should be able to tell what is going on. Feel free to leave a comment if you do have any questions.
Now, fire up the web server and head to localhost:3000/forums/1/topics/1 (assuming you have a topic already created).

And create a couple of replies:

And thats it! (10 points go to anyone who catches the video game reference in reply #2 in the screenshot).
In this tutorial, we nested replies inside of a topic, and set the views/controllers accordingly. Next time, we will take a look at authentication using the RESTful authentication plug-in and acts_as_state_machine plugin.
Feel free to leave comments, and link me from your site. See you next time!
Thank you for this great tutorial.
ReplyDeleteI'am looking forward to the last part.
Marek
Flamestrike! Ultima online references in the screenshot.
ReplyDeletechouinard += 10
ReplyDeleteThank you for making RoR seem less like Harry Potter & more like Transformers. Currently I am having problems with line 29 in topics/show.html.erb should there be [square brackets] around the params in edit_topic_path(@forum, @topic)?
ReplyDeleteRegards,
Bill
(ahhhhhh, just answered my own question)
ReplyDeleteI just found your tutorial and it's excellent. Also the video game reference would the the words of power from Ultima Online
ReplyDeletelol "kal vas flam" !
ReplyDeleteNice to see some UO fans out there!
Great tute, I'd love to see some comments on Ruby syntax, I can't find a tutorial where learning Rails has some dashes of Ruby thrown in...
Part 2 is nice reinforcement for what was learned in part 1, and some of the differences provided insight into the workings of MCV.
ReplyDeleteThe only thing I did a bit differently was to add
validates_presence_of :subject, :body
to the app\models reply.rb and topic.rb files.
you blog is very helpfull!thanks.
ReplyDeletethe topics/show.html.erb
last line is better replace by this:
= link_to 'Back', forum_path
When I click the "Reply" button,
ReplyDeleteI got this error:
ArgumentError in RepliesController#create
wrong number of arguments (2 for 1)
RAILS_ROOT: D:/railsProjects/myforum
Application Trace | Framework Trace | Full Trace
app/controllers/replies_controller.rb:54:in `[]'
app/controllers/replies_controller.rb:54:in `create'
app/controllers/replies_controller.rb:51:in `create'
Pls give me some suggestion.
Thanks.
When click a topic,then get the following error:
ReplyDeleteNoMethodError in Topics#show
Showing topics/show.html.erb where line #15 raised:
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.subject
Extracted source (around line #15):
12:
13: unless @topic.replies.empty?
14:
15: @reply.subject
16:
18: @reply.body
RAILS_ROOT: D:/railsProjects/myforum
What's the problem here?
Could anyone help me?
Thanks.
It works for me now.
ReplyDeleteFind the problem.
I'm getting a problem after a reply:
ReplyDeleteundefined method `replies' for # < Topic:0xb7458348 >
RAILS_ROOT: /home/friedman/public_html/studentsfirst
Application Trace | Framework Trace | Full Trace
/usr/lib/ruby/gems/1.8/gems/activerecord-2.1.2/lib/active_record/attribute_methods.rb:256:in `method_missing'
app/controllers/replies_controller.rb:51:in `create'
Could you be of any help??
Thanks,
Jacob
Never mind! Fixed it. Was a typo in a model. Figures...
ReplyDeleteAgain, thank you for your time to make such a great tutorial. Your efforts are nothing less than noble.
nice tutor, keep it up..
ReplyDeleteGoodness, there's a great deal of useful info in this post!
ReplyDelete