A bit more on scoring

Playing around, just noticed that articles were disappearing after i clicked on them – this is left over from my old system where non-article links were awarded 0 points, and that was my system for deciding whether or not to show them. In hindsight this was a pretty stupid method – i should have marked them as link or article with a column in the articles table. As it happens i decided to add links to the article list anyway so it’s not necessary.

Anyway, i now need to make the _article_list show all articles, not just ones with a positive score. OK, done that, now i’ve noticed a new problem: the articles are paginated, 20 to a page. However, they’re not being sorted before pagination, only after pagination: so i have articles on page 2 with more points than the lower articles on page 1. This is probably because i’m doing my sorting in the list – i should probably have the sort method in the model somehow.

Put the <=> method in the model, still getting the same result though. Time to go and do some pagination research…

OK, that’s all working great now: here’s what i did. First, a controller method to generate a Paginator and the current set of pages: this replaces the scaffold-generated list method which did the same sort of thing but in a more rails-default way.

def list
#@article_pages, @articles = paginate :articles, :per_page => 20
#get the required page

page = (params[:page] ||= 1).to_i
items_per_page = 20
#get index of first item on current page

offset = (page – 1) * items_per_page
#count the items
item_count = Article.count

#create a paginator
@article_pages = Paginator.new(self, item_count, items_per_page, page)

#get the required subset of items
@articles = Article.find_page(:items_per_page => items_per_page, :offset => offset)

This calls an Article class method called find_page, which calls Article.find with various params:

def Article.find_page(params = {})
defaults = {:items_per_page => 20}
if params[:offset] == nil && params[:page_size] != nil
offset = params[:page_number] * params[:items_per_page]
offset = params[:offset] || 0
return Article.find(
:order => “points DESC, added_at DESC”,
:limit => params[:items_per_page],
:offset => offset

Note the :order param: this is telling the sql query to order by points (descending) and then by added_at (descending).

All that’s left now is to change the view code a little bit:

I made the previous/next page show up as text rather than a link if there’s no page to link to:

<% if @article_pages.current.previous %>
<%= link_to ‘Previous page’, { :page => @article_pages.current.previous } %>
<% else %>
<span>Previous page</span>
<% if @article_pages.current.next %>
<%= link_to ‘Next page’, { :page => @article_pages.current.next } %>
<% else %>
<span>Next page</span>
<%end %>

The rest is the same: for article in @articles etc.

One bit which i partly got from the web page that explained all this, replaces the previous page/next page bit with a nice
” << 1 2 3 4 >> ” set of page links:

<%= (@article_pages.current.previous)? (link_to ‘<<‘, { :page => @article_pages.current.previous }) : “<<” %>

<% for page in @article_pages -%>
<%= link_to_unless((params[:page].to_i == page.number), page.number, :page => page) %>
<% end -%>

<%= (@article_pages.current.next)? (link_to ‘>>’, { :page => @article_pages.current.next }) : “>>” %>

I’ve put this into a partial called _page_links, which i call at the top and bottom of the list.

There’s a few enhancements i’d like to make – for example, to ‘list only my articles’, and when you go back from an article to go to the list page with that article. But i’m going to leave that for now and press on with finishing off the points: all that needs to be done now is to replace the ‘score up/down’ text with arrow icons, which are greyed out if you’ve already done them on that article.

First, let’s make the link not show up if you’ve already marked it that way: (more conditional operator usage)

<%= (article.score_from_user(:user_id => session[:user]) != 1) ? (link_to “score up”, :controller=>”article”, :action => “change_score”, :article_id => article, :user_id => session[:user], :points => 1) : “scored up” %>

And similarly for ‘scored down’. Next step is to simply replace the output text with image tags – i need 4 : two directions x two colours (green for up, red for down, grey for neither). I did these before, i think…

OK, done! The rather long conditional statements now look like this – i’ve edited them a bit to make the structure clearer:

<%= (article.score_from_user(:user_id => session[:user]) != 1)
(link_to image_tag(“/images/up_arrow_grey.jpg”, :alt => “up”, :border => 0), :controller=>”article”, :action => “change_score”, :article_id => article, :user_id => session[:user], :points => 1)
(image_tag(“/images/up_arrow_green.jpg”, :alt => “up”, :border => 0)) %>

Done a bit of tidying up of the graphics. What i’ve also been trying to do is to work out how to go to a given page: when you mark an article up or down, it sometimes goes onto a different page. I’d like in this case to switch the view to that page as well.

So, at the end of change_score i can write

redirect_to :controller => “article”, :action => “list”, :page => page

But the problem is getting a value for ‘page’. One thing i’m trying is to first get the ordered array of all articles, with

articles = Article.find(
:order => “points DESC, added_at DESC”

If i could then get the position of the desired article in this  list, i could work out what page it’s on from the ARTICLES_PER_PAGE constant i put in environment.rb:

page = (position/ARTICLES_PER_PAGE.to_i) + 1

But i can’t work out how to get the position.  I can do another sql search but that seems really wasteful:

article = Article.find(params[:id])
position = articles.index(article)
#this is returning one page too low!  why???
return (position/ARTICLES_PER_PAGE) + 1

This is inefficient and also consistently returns one page too low.  I don’t know why.   However, when i call it properly (instead of with a test id) from the end of change_score, it seems to work.  I’ve asked on the forums about the best way to get the page/position of an item, hopefully someone will come back with a more efficient method.

So – the points stuff is pretty good now.  That’s enough for today.


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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s