Back from holiday

September 3, 2007

Ahhh…holiday was great. Now i have 3 weeks left to finish and write up my degree project. wahey! I need to leave the video conversion business for now and get the thing online and usable.

Orginally i was hoping to get my uni to host a rails server – they seemed up for it but hadn’t done a rails server before, which made me a bit nervous – my uni is two hours travel away and i didn’t want to have to go in to try to get it up and running too often. So, i decided to go with a paid web hosting service. I’d been looking at BlueHost and a friend suggested them, so i went with them. $7.95 a month for 12 months, comes out at about £48 for the year.

I decided to change the name to http://www.reeplies.com, with the ‘stories’ that people attach to articles renamed to ‘reeplies’ – this makes the focus of the site more on the reeplies than the articles, which is good.

Anyway, going through the bluehost setup wizard at the moment. After getting it set up i’m going to get capistrano up and running so i can update it easily.

OK, finished the wizard, now i need to set up my stuff for uploading to the server. Before i do that i’m going to do that rename of stories to ‘reeplies’. Most of it is just simple cosmetic stuff, but one thing that isn’t is the urls: instead of the url for editing a story being

http://www.reeplies/story/edit/84

i want it to be

http://www.reeplies/reeply/edit/84

This involves routes, which i’ve seen referenced but not used. To skip past all my fumbling about and forum questions, this is how i did it: in config/routes.rb, add the following line:

map.story ‘/reeply/:action/:id’, :controller => “story”

Amazingly, this is all that’s required – i don’t have to change any of my link_to or redirect_to parameters – it just works! Outstanding!

I did have one little problem that puzzled me for a while – my reeply form submission wasn’t working. This turned out to be due to a case statement that i was using to detect which button the user pressed on the form page (they could submit the form, add a picture or add a movie). The case statement works by looking at the value in params[:commit], which contains the text displayed on the buttons – the easiest/only? way to tell them apart in the way i set it up. So i had to change the case condition to use the updated button text “Submit Reeply”. Sorted.

So, i think it’s ready to get hosted now. Obviously there’s still lots of stuff i want to fix but it’s useable and has the right names for stuff. So, on with the hosting setup!

One day later, no progress. One of the brighton Ruby group people has kindly offered to help, a bit later in the week. So, in the meantime, i’m going to do a bit of tweaking – in particular, to let the user rate reeplies up and down. This will work pretty much the same as for articles – for example, a reeply added by a user will automatically be marked as having been scored up by that user.

So, just as we had a ‘scores’ class that had user_id, article_id and points, we now need a story_scores class to do the corresponding task for stories/reeplies. (i’m calling it story_scores as reeplies are still called stories everywhere in the code). Time to go back and read my old blog post about setting up scores! Actually, on rereading that i remembered that i revised it loads as i went along, making it messy and hard to follow. So, here’s the final working version, but for stories this time.

At the command line:

ruby script/generate model StoryScore

In the new story_score.rb file:

class StoryScore < ActiveRecord::Base
belongs_to :story
belongs_to :user
end

In story.rb:

has_many :scores, :through => :story_scores
has_many :scorers, :through => :story_scores, :source => :user

A migration to make the table –

class CreateStoryScores < ActiveRecord::Migration
def self.up
create_table :story_scores do |t|
t.column :story_id, :integer
t.column :user_id, :integer
t.column :points, :integer
end
end

def self.down
drop_table :story_scores
end
end

A change_score method for story_controller:

def change_score
begin
@story = Story.find(params[:story_id])
rescue ActiveRecord::RecordNotFound
flash[:message] = “Couldn’t find Reeply##{params[:story_id]}, redirecting to article list.”
redirect_to :controller => “article”, :action => “list”
else
flash[:message] = @story.change_score(params)
redirect_to :controller => “article”, :action => “show”, :id => @story.article_id
end
end

And the story model method it calls:

def change_score(params = {})
new_points = params[:points].to_i
@score = StoryScore.find(:first, :conditions => [“story_id = ? and user_id = ? “, params[:story_id], params[:user_id]])
if @score == nil
#@score not found
#no score exists from this user so make a new one then add the points

return_string = “Didn’t find score, making a new one”
@score = StoryScore.new(:story_id => params[:story_id], :user_id => params[:user_id], :points => new_points)
self.points += new_points
@score.save
self.save
else #found something for @score in db already
if @score != nil #@score was already there
#look for a nil and turn it into a 0 if present
@score.points = 0 if @score.points == nil
if @score.points != new_points
#score already there but points are different
#subtract old points then add new points

self.points -= @score.points
self.points += new_points
return_string = “changed your score for this story from #{@score.points} to #{new_points}”
#then change @score to have new points
@score.points = new_points
@score.save
self.save
else
return_string = “you already gave this story #{@score.points} point”
end #if
end #if
end #if
return return_string
end #method

And the simple story model methods called by the above method:

def score_from_user(params = {})
begin
score = StoryScore.find(:first, :conditions => [“story_id = ? and user_id = ? “, self.id, params[:user_id]])
return score.points
rescue
return 0
end
end

def change_points(params = {})
self.points += params[:amount]
self.save
end

Cool, that all seems to be working ok! Noticed a couple of bugs though:

  • Uncompleted reeplies (drafts) are being listed alongside completed reeplies – they shouldn’t be.
  • When you add a reeply, it should be scored up by you automatically.

Fixed the first problem by adding the following to the relational info at the start of the article class:

has_many :complete_stories,
:class_name => “Story”,
:conditions => [ ‘complete = ?’, true ]

This is a top tip i just got from a forum, i’m going to look into doing more of this sort of stuff.

Now in the view, instead of saying

for story in @article.stories

I just say

for story in @article.complete_stories

Next – i want to call change_score from the create methods of story and article.  Here’s how I do it:

When a story is created, its points are set to 0.  There’s no StoryScore record for it at this stage.  When the user hits the ‘submit’ button to submit the completed story, the submit method of the Story model is called.  I simply added a line to the submit method to call change_scores with 1, so it’s just as if the user hit the “score up” button when they submitted the story.

Next is to do the same for articles…ok.

I also want to check if there’s an article there already, and if so then score it up for the current user, instead of re-adding it.

Done that, in the add method of the article controller:

def add
#first of all, see if we already have an article with the passed url.
#if we do, we just want to score it up for the current user (and inform the user that we did it)
if (@existing_article = Article.find(:first, :conditions => [“url = ?”, params[:article][:url]]))
@existing_article.change_score :user_id => session[:user].id, :points => 1
flash[:notice] = “This article is in Reeplies already.  It has been automatically scored up for you.”
redirect_to :action => ‘list’
else
@article = Article.new(params[:article])
@article.user_id = session[:user].id
if @article.save
flash[:notice] = ‘Article was successfully created.’
@article.change_score :user_id => session[:user].id, :points => 1
redirect_to :action => ‘list’
else
render :action => ‘new’
end
end
#if else
end

One last thing – for each story, i want to list Reeplies in order of points.  This turns out to be super-easy!  I already set up a new associated called complete_stories, so i just add an ‘order’ parameter into that!  So my complete_stories association, in Article, looks like this:

  has_many :complete_stories,
:class_name => “Story”,
:conditions => [ ‘complete = ?’, true ],
:order => “points DESC, added_at DESC”

Sweet!  That’s a good point to end on today i think.

Advertisements

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: