Creating comments – saving user_id and article_id with comments.

July 20, 2007

First thing today is to carry on hooking the login stuff into everywhere it needs to be hooked into. So far we have articles and comments – the article user name registration is working fine (as far as i’ve seen anyway), next step is to make comments old the user id too, and display it underneath. Also, when someone wants to leave a comment the system should check they are logged in and if they need to get taken to the login screen it should take them back to the comment page afterwards.

First step – change the model: Comment belongs_to :user and User has_many :comments.

Done that. Go to have a look at the show page (realised this also needs a login box) – woah! got this message which i’ve never seen before –

Status: 500 Internal Server Error Content-Type: text/html

 

We’re sorry, but something went wrong.

We’ve been notified about this issue and we’ll take a look at it shortly.

This must be generated by rails somehow. Could it be related to my model changes? It seems unlikely…but on undoing the changes, it’s working again!

It looks like this went wrong because i tried to put two ‘belongs_to’ targets on the same line (as is the usual ruby syntax): i wanted to say that a comment belongs to an article and belongs to a user, so i wrote (in comment.rb)

belongs_to :article, :user

However, it looks like this isn’t allowed. You need to say

belongs_to :article
belongs_to :user

This seems at odds with the usual ruby syntax. Oh well, least i fixed it.

While looking this up, i saw how many-to-many relationships work in rails, which i was wondering about for when i get to links and stories, which could be attached to several articles (ie an article has many stories and a story has many articles). The way this works in the DB is that we have a new table, a join table, that has pairs of story_id and article_id. We’ll get to that in a bit i think when it comes to links and stories. For now, a comment only goes with one article so we’re ok with 1 to many.

Made sure that the comment displaying code handles gracefully the situation where it doesn’t have a user_id and/or added_at time stored – it just gives the info it can:

<% for comment in @article.comments %>

<%= comment.body %>

<%= “Added” if comment.added_at or comment.user_id %>
<%= “#{time_ago_in_words(comment.added_at)} ago ” if comment.added_at %>
<%= “by #{comment.user.login}” if comment.user %>

Entered some data for user_id in a comment (by hand, in the DB) to test the above, that seems to work. Also added a field called added_at, which holds the datetime the comment was added at. However, this doesn’t seem to want to ‘stay in’ the DB – i enter a datetime, either manually or via a quick rails line, and when hit refresh in the DB, it’s not there. This is very odd. I’m going to do a proper comment adding page, and see if that works any better. Maybe i don’t have the scaffold set up – i don’t have any controllers for comments for example, so maybe that’s something to do with it.

Got a comments form displaying at the bottom of the list of comments. This is good enough for now – eventually i’d like to have the ability to reply to individual comments but that’s not needed for the basic 1st version. The code is simple:

<%= form_tag :action => “comment”, :id => @post %>
<%= text_area “comment”, “body” %>
<%= submit_tag “Submit comment” %>

As with DHH’s tutorial though, the app breaks when we hit submit, because we haven’t done a controller action to handle the submission.

After a bit of experimentation, got the comment form in show.rhtml and the comment method in newspipe_controller to communicate and set up a comment properly. It’s kind of obvious once you know how it’s done, i think i understand it a lot better now. I’m going to do a line by line breakdown:

From show.rhtml (ruby only, without the markup)

<%= form_tag :action => “comment”, :id => @article %>

This call form_tag, which is a standard rails method that builds an html form. We pass it two parameters: action=>”comment”, ie call the action called comment when we submit, and :id=>@article, which is just another parameter that gets passed through the the comment action. There’s nothing special about the name ‘id’ in this case – we could call it whatever we want, as long as we remember to refer to it by that when we use it in the method. As usual it’s best to stick with the most obvious name for everything.

<%= text_area “comment”, “body” %>

text_area is a helper that generates a text area component in an html form. The first parameter is the name of the field, and the second is the value that is used to populate the field. At the moment, body has no value so the field is blank, but we could put something in there if we wanted. NOTE TO SELF: This is what i wanted to do with the title, in the new article page – fill it in automatically after the url has been entered. Anyway…

<%= submit_tag “Submit comment” %>

Generates an html form submit button, and names it. It doesn’t say where the data is to be sent, we did that at the top of the form.

The next step is the controller code – the “comment” action to be specific. This is passed through two arguments – a variable called :id, equal to the article the comment is to be associated with, and also the body, which contains some text. Here’s the complete code:

def comment
@comment = Article.find(params[:id]).comments.create(params[:comment])
@comment.added_at = DateTime.now.to_s
@comment.user_id = session[:user].id
if @comment.save
flash[:notice] = “Added your comment.”
else
flash[:notice] = “Sorry, your comment was not able to be saved.”
end
redirect_to :action => “show”, :id => params[:id]
end

And now the step by step:

def comment
@comment = Article.find(params[:id]).comments.create(params[:comment])

Big one this: use the :id parameter to get an article from the DB. Then, get that article’s comments list (an array of comments), and create a new comment using the comment data passed through from the form fields. Then set it to an instance variable called @comment.

@comment.added_at = DateTime.now.to_s
@comment.user_id = session[:user].id

These lines fill in the fields that are autogenerate, rather than entered by the user – date/time and the user_id (which we want to be equal to the currently logged in user, who we get with session[:user]

if @comment.save

The data passed through with the form will have been automatically added to the db when we used create. However, the stuff with the date/time and the user was only added to @comment, which is just a local instance variable, and won’t go through to the DB unless we make it. We do that by saving the comment. Above, we test if it saved while we save it.

flash[:notice] = “Added your comment.”
else
flash[:notice] = “Sorry, your comment was not able to be saved.”

end

If it did, we put a message on the flash ‘noticeboard’, which layout.rhtml (the master template for all pages) displays the contents of at the top of any page. (this is a bit amateurish, i’d like to do something a bit smarter but it’s fine for now). If it didn’t we just display an alternative message. Again, it might be better to do something else ultimately, but for now this is fine.

redirect_to :action => “show”, :id => params[:id]
end

Whether it was saved or not, take the user back to the show page for the article that was passed through the the method.

That’s it for today.  Didn’t get a huge amount done, but got some new stuff working, and got my head around some things that i’d just kind of copied over before.  Having the book is great as for the first time i can understand how methods actually work.

Wait, just thought of something else – i should force people to log in before adding a comment.  Hang on…

Added ‘comment’ to the list of actions in ApplicationController, but it didn’t work:

before_filter :login_required, :only =>[‘welcome’, ‘change_password’, ‘hidden’ ] 

Also found that the same list is in UserController as well.  This seems odd for it to be in two places.

Ah wait – that explains it.  The filter has to be in the same place as the methods it’s watching out for.  That explains why its in more than one controller, but it doesn’t make sense for it to have the same list of methods in each controller – in each case the methods should be different since they’re methods of that class.

Anyway – added ‘comment’ to the login_required list in newspipe_controller, and it does now take you to the login page.  However, once you log in you’re not taken back, although the comment does seem to be entered into the DB, albeit without a datetime or user_id.  Also, i can’t seem to log in at all now!  gah…. took that ‘comment’ addition out of NewspipeController but login is still broken.  What the??

Checked the registering as a new user, and that still works.  Maybe the user data in the db has been broken so it’s not recognising the password any more. Logged out and logged back in fine as my new user, which supports that theory.  What happened though?  Having the passwords encrypted in the db is making it hard to tell what’s going on, i think i’m going to make a new field to store the unencrypted ones along side the encrypted, just for my own sanity.

I should add some more feedback to the login screen really so it tells you why login failed.  Wait – it already uses a flash to say “login unsuccessful” but i’ve never seen it.

Hmmm.  Anyway, that’s definitely enough for today.

Advertisements

One Response to “Creating comments – saving user_id and article_id with comments.”


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: