SitePoint Sponsor

User Tag List

Results 1 to 6 of 6

Hybrid View

  1. #1
    SitePoint Zealot
    Join Date
    Jul 2005
    Posts
    124
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Ruby code, up for some (Hopefully.) constructive criticizm.

    In an attempt to learn more about Ruby I've decided to create a feed reader. Nothing terribly complex yet, the code and an example of it's usage (Lines 28 through 45.) can be seen here:



    A bit of an overview along with my thought process:

    Line 2 - Shortcut accessor, example usage on line 40.
    Line 6 - Even though there are modules for RSS versions 0.9 and 1.0, the module for version 2.0 appears to be reverse compatible.
    Lines 11 and 12. - Inbound assignment along with a little bit of regex trickery to remove an leading "http://". Makes me question the proper use of instance variables.
    Line 18 - An early attempt at error handling, it took quite a bit of digging to find the get_response function.
    Lines 21 through 23. - Add the entries.

    A live example of the output can be found here. There are no comments yet, as the more that I read into this the more often things change. I've also got to figure out how to escape the entities in the 6th group on the 5th entry so that they can resemble what's used in the feed. Just an attempt at learning Ruby and an early iteration, thanks alot for any insight, it's appreciated.

  2. #2
    SitePoint Guru
    Join Date
    Aug 2005
    Posts
    986
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I have altered your code a little:

    Code:
    require 'open-uri'
    require 'rss/2.0'
    
    def get_feed_entries(address, total_entries = 5)
      open(address) do |feed|
        channel = RSS::Parser.parse(feed.read, false).channel
        items = channel.items[0...total_entries]
        items.map do |item|
          item.title.strip
        end
      end
    end
    
    feeds = [
      'http://www.1lotus.com/rss/2.0/journal_all.xml',
      'http://feeds.feedburner.com/hicksdesign/']
    
    for url in feeds
      feed = get_feed_entries(url)
      
      feed.each_with_index do |entry, i|
        puts "#{i + 1}. #{entry}"
      end
    
      puts
    end
    Let me explain:

    line 1: open-uri is a standard Ruby library that lets you open a document on the web with the function open(url). This way you don't have to do magic with regexes to get the right format for the url (open can figure everything out).

    line 4: I used a function instead of a class because it is very simple, and a class with one menthod doesn't make sense. You can always wrap it in a class if you need more functionality. But I prefer an approach that uses functions because it is often simpler. I also gave total_entries a default value, so we can omit it in most calls.

    line 5: Here you see the call to open. It also uses a block which will be called with the downloaded result.

    line 6: Parse the feed and extract the channel

    line 7: the items[0...total_entries] removes all entries beyond total_entries. I used three dots because we want 5 entries. If there were 2 dots, Ruby would extract the first 6 entries.

    line 8 + 9: If you ever want to take an array, do something with the items in it, and put the result in an array, map is your friend, because it does it for you. It "maps" all items to item.title.strip, and returns the result (so it does not modify the original, if you want that, use map!). Note that we don't need an explicit return statement, because the last thing in a function will be returned automatically.

    line 14..16: I use literal notation for arrays. It is more convenient than foo = Array.new; foo[0] = ...; foo[1] = ...;

    line 18..23: Nothing special here.

    line 25: if you call puts without arguments, it prints a newline. (so you don't need "\n")
    If you have any questions, please ask!

  3. #3
    SitePoint Zealot
    Join Date
    Jul 2005
    Posts
    124
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Wow, thanks for the response. I was actually using OpenURI at first, but there were two problems:

    1) The project is hosted on my personal website, which doesn't have the OpenURI module installed. I realize it's not big deal as I can just load it with a relative path, it's just an annoyance.

    2) When a website is down it responds with a fatal error. I read through the section on error handling in the second edition of Programming Ruby, nothing worked as I would like. So I used the Net/HTTP module on line 18 to check for a successful connection.

    I'm definitely going to have to check out everything else, thanks so much for the response, I appreciate it. On another note, was my use of instance variables accurate? I figure if I use them on inbound (Variables being assigned from parameters.) and outbound (Variables that I would typically use accessor methods for.) I would be good to go. Thanks again!

    Edit: I've cleaned things up a bit:



    9 lines shorter and much clearer, though I'm still not sure if I'm using instance variables as intended. Anyways, thanks again, I appreciate it.
    Last edited by IAIHMB; May 15, 2006 at 14:24.

  4. #4
    SitePoint Zealot
    Join Date
    Jul 2004
    Location
    Oklahoma
    Posts
    119
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Code:
    require 'open-uri'
    require 'rss/2.0'
    
    def get_feed_entries(address, total_entries = 5)
      open(address) do |feed|
        channel = RSS::Parser.parse(feed.read, false).channel
        items = channel.items[0...total_entries]
        items.map do |item|
          item.title.strip
        end
      end
    end
    
    feeds = [
      'http://www.1lotus.com/rss/2.0/journal_all.xml',
      'http://feeds.feedburner.com/hicksdesign/']
    
    feeds.each do |url|
      feed = get_feed_entries(url)
      
      feed.each_with_index do |entry, i|
        puts "#{i + 1}. #{entry}"
      end
    
      puts
    end
    Use the iterator syntax, instead of the literal for X in Y. This is considered better ruby practice, and allows for easier iteration of non-standard objects.

  5. #5
    SitePoint Guru
    Join Date
    Aug 2005
    Posts
    986
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    for x in y delegates to obj#each. That means that you can use for x in y on every object that supports #each. I find the for loop easier to read (just taste).

    @poster: your use of instance variables is accurate, but you could use an Entry class, instead of an array of entry titles (that would be more oo, and more extensible if you want to read the content of each post too.

  6. #6
    SitePoint Zealot
    Join Date
    Jul 2004
    Location
    Oklahoma
    Posts
    119
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Fenrir2
    for x in y delegates to obj#each. That means that you can use for x in y on every object that supports #each. I find the for loop easier to read (just taste).
    True, the result is the same. But getting used to each, as opposed to for x in y, makes each_with_index, map, and inject, feel a little easier, and look more natural in your code. The ruby mailing list tends to consider the each syntax the "more standard" method, but of course, to each his own


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •