Finally, some project code! (plus, using gems)

July 17, 2007

Started the actual project code today. Set up a new project in rails. So far i have a list of web pages (added manually by me using the default interface), and when you click on one you go to the ‘view’ page, which will copy over all of the html from the ‘proper’ page, then add my newspipe stuff at the bottom.

First hurdle is getting at a page’s html, to copy it into my rails page. Turns out there’s a ruby gem called hpricot which is an html parser, which sounds ideal. So, i’m going to be installing my first ruby gem, which is quite exciting – been meaning to look into gems for ages.

OK – downloaded the gems installer and ran it. The first gem i tested was ‘progressbar’, which is a text progress bar library. To use the gem you just write

require ‘progressbar.rb’

at the top of the file. I tried a progress bar example, but it couldn’t find the file. I did a search in windows, and i did have the file, in one of the instantrails folders. Ruby wasn’t finding it though. Instead, i used the gem installation command to install it from the gems server, and it worked then:

gem i -r progressbar

So, got hpricot by the same method. Now to test it out, outside of rails.

Seems simple – need to requires – ‘hpricot’ and ‘open-uri’ (the latter is necessary to create an hpricot object from a url). Then you have an object with a lot of parsing methods, and of course a to_s method so you can print the html. Now, when i do my rails page, i’m going to need to print only part of the html page – the body perhaps?

Worked first time! The code is this:

<% require ‘hpricot’ %>
<% require ‘open-uri’ %>

<%= (Hpricot(open(@article.url))/”body”).inner_html %>

/”body” is just the hpricot syntax for selecting an element. In this case i know there will only be one “body” element, but if there were multiple, eg if i’d selected /”p”, then i’d get an array of p elements. Since there’s one i can refer to it simply by name. inner_html shows the htm content of the element, ie not including the start/end tags.

Done a bit of reformatting and rearranging – i’ve now got this at the top of the page:

This is a Newspipe article

Added at Tue Jul 17 13:33:00 +0100 2007
points
See the article in its original context
Go back to the article list


Skip to main content

Followed by the entire story (ads and everything), then another line with another ‘go back’ link at the bottom.

Outstanding tasks (still-to-fix in red):

  • Want to change the page title to “Newspipe – <%= @article.title %>” but can’t work out how.
  • The action in the article model for printing the score isn’t working. I don’t think i’m getting at the instance variables properly. It should be :points but that doesn’t seem to work.
  • Want to change the time to “5 minutes ago” or similar, instead of absolute time.
  • On the list page, want to list items by points, descending order.

Overall I’m pretty happy with it though. Going to put in my tables for the links, user stories and comments next.

Solution to the title problem above: the way the title works is that there’s an rhtml page called views/layouts/newspipe.rhtml that is the basic html generator for every page. It has some html settings, a title and a body, where the body simply yields to the calling code (and the calling code generates the code to go into the body of the page).

By default, the title element calls the builtin controller method ‘show_action’, which just lists the current action’s name – “list”, “show”, “edit” etc. I made a new method in the controller –

def get_title
case action_name
when “show” then return Article.find(params[:id]).title
when “list” then return “List of articles”
else return action_name
end
end

This looks at what the current action is and then generates an appropriate title – in the case of the “show” page, which views the article, it uses the title of the article, which it finds using the local variable :id. If i try to call this on the listing page it will break, since on the list page no article is selected and therefore there’s no local :id variable to use. So for the list page i just use a fixed string for now. Maybe later i’ll come up with something more dynamic, it’s probably fine as it is though.

Solution to the ‘list posts in points order’ problem: this was easy. In the for loop which says

for article in @articles

i sorted @articles to produce a sorted copy. The trick with sort is just to pass over the ‘rules’ for comparing two items |a,b| thrown out by the sort method:

for article in @articles.sort{ |a,b| b.points <=> a.points }

Putting b before a makes the sort come out in descending order, which is what i wanted.

Solution to the ‘added 5 minutes ago’ problem – another controller method.

def time_since
seconds = (Time.now – @article.added_at).to_i
minutes = seconds/60
hours = minutes/60
days = hours/24
weeks = days/7
months = weeks/4
years = weeks/52

if seconds < 5 then return “just now”
elsif minutes < 1 then return “#{seconds} seconds ago”
elsif hours < 1 then return “#{minutes} minute#{“s” if minutes > 1} ago”
elsif hours < 5 then return “#{hours} hour#{“s” if hours > 1} and #{minutes – hours*60} minute#{“s” if (minutes – hours*60) > 1} ago”
elsif days < 1 then return “#{hours} hours ago”
elsif weeks < 1 then return “#{days} day#{“s” if days > 1} ago”
elsif months < 1 then return “#{weeks} week#{“s” if weeks > 1} ago”
elsif years < 1 then return “#{months} month#{“s” if months > 1} ago”
else return “#{years} year#{“s” if years > 1} and #{months – years*12} month#{“s” if (months – years*12) > 1} ago”
end
end

I thought this should be in the model, but i couldn’t get it working in the model and don’t know why – i seemed to be having problems accessing the instance variables. :title or @title didn’t seem to work. So i did it in the controller instead. Need to work this out, it’s really bugging me.

Change to this solution – someone on a forum told me about the time_ago_in_words(time) library method, which already does the job, so i just used that instead. doh.

Solution to the scores not being printed. Again, just moved the method from the model to the controller. But unsatisfied with this but it works.

Change – for displaying the existing page, instead of copying all the html into the body of the layout, i found out (again off a forum) about iframe elements, which show an existing web page inside a nice scrollable frame, layout all perfect. So switched to using that instead. The hpricot stuff will still be handy for some other things though i think so it was worth getting anyway.

I’m really pleased with how much i got done today, and how much i learned from the forums etc.  A very productive day!  That’ll be the rails effect :)

Leave a comment