How to create a twitter section

Here's an example of how to create a section that will read tweets from a specified user. After you've created the section you can put it anywhere on your website to show your latest Twitter updates. The tweets will be cached into a yaml file to minimize requests and just in case Twitter is unavailable.

So let's get started. First create the following folder structure and files.

The files

Root
 |-app
   |-models
     |sections
       twitter_section.rb
   |-templates
     |-sections
       |-twitter_section
         |-default
           index.html.erb
   |-views
     |-skyline
       |-sections
         _twitter_section.html.erb

The migration

A section is a subclass of Activerecord::Base so we'll need to create a migration for it.

$ ./script/generate migration CreateSectionsTwitterSection

For this example the only variable field of the section is the user, but you could also use it to set the maximum amount of tweets you want to show.

class CreateSectionsTwitterSection < ActiveRecord::Migration
  def self.up
    create_table :sections_twitter_sections do |t|
      t.string :user

      t.timestamps
    end
  end

  def self.down
    drop_table :sections_twitter_sections
  end
end

The model

All the sweetness of our Twitter section will go into the model. This is where we will read the tweets, write them into a yaml file and make them available.

It's pretty self explanatory. The tweets method makes a call to fetch_tweets which checks if there are cached tweets. If there's no cache or if the cache has timed out it will connect to Twitter to get the new tweets.

Of course if it can't connect and there is an old cache file, it will just use that one.

Twitter API

Checkout the twitter API on: http://apiwiki.twitter.com/ to get comfortable with all the possibilities of Twitter.

For this example we've just used statusses/timeline.json to display the timeline of a user, but there's a lot more you can do.

So here's the code for the model

require 'open-uri'
require 'fileutils'
class Sections::TwitterSection < ActiveRecord::Base
  set_table_name :sections_twitter_sections
  extend ActiveSupport::Memoizable
  include Skyline::Sections::Interface
  
  cattr_accessor :cache_path
  @@cache_path ||= Skyline::Configuration.twitter_section_cache_path  

  cattr_accessor :cache_timeout
  @@cache_timeout ||= Skyline::Configuration.twitter_section_cache_timeout
  
  after_update :delete_from_cache
  after_destroy :delete_from_cache
  
  def tweets
    self.fetch_tweets
  end
  
  protected
  def cache_file
    File.join(self.class.cache_path, "#{self.id}.yml")
  end
  memoize :cache_file
  
  def fetch_tweets
    if File.exists?(self.cache_file) && File.mtime(self.cache_file) > Time.now - self.cache_timeout
      tweets = YAML.load(File.read(self.cache_file))
    else
      if tweets = get_tweets_from_twitter
        cache(tweets)
        tweets.collect!{|t| t['user']['screen_name'] + " tweeted " + t['text']}
      elsif File.exists?(self.cache_file)
        self.reset_mtime
        YAML.load(File.read(self.cache_file))
      else
        false
      end
    end
    tweets
  end
  memoize :fetch_tweets
  
  def url
    "http://twitter.com/statuses/user_timeline.json?screen_name=#{self.user}"
  end
  
  def get_tweets_from_twitter
    feed = ::ActiveSupport::JSON.decode open(self.url).read
    rescue
    logger.error "[TwitterSection] Failed to fetch!"
    false
  end
  
  def cache(feed)
    File.open(self.cache_file, "w") do |f|
      f.write feed.to_yaml
    end
  end
  
  def reset_mtime
    FileUtils.touch(self.cache_file)
  rescue
  end
  
  def delete_from_cache
    File.delete(self.cache_file)
  rescue
  end
end

The template

There's not much going on in the template here. As a title I've put in the user that we're diplaying the tweets from and I've chosen to only show the 5 last tweets.
The json object comes back with loads of fields, for this example I'm only interrested in the text field, hence the t['text']. I've used the auto_link helper to let rails create links if there are any.

Now it's up to you to make some fancy css to go with the section.

<dl class="info">
  <dt><%=twitter_section.user%></dt>
  <dd>
    <% if twitter_section.tweets.present? %>
      <div class="text">
        <% twitter_section.tweets.each_with_index do |t,i| %>
          <% if i < 5 %>
            <p><%= auto_link(t["text"].to_s) %></p>
          <% end %>
        <% end %>
      </div>
    <% end %>
  </dd>
</dl>

The view

The section will also need a form to set the username when we add it to a page. SkylineCMS renders a partial with the section name from views/skyline/sections

The form definition is all doen by skylineCMS so we only need to add the fields.

<table class="fields">
  <tr>
    <th><%=sectionable_form.label_with_text :user %></th>
    <td><%=sectionable_form.text_field :user %></td>
  </tr>
</table>

The configuration

The only thing left is adding the following lines to your skyline_configuration.rb in config/initializers.rb

This sets your cache path and timeout and adds your new TwitterSection to your sections.

config.twitter_section_cache_path = File.join(Rails.root,"tmp/cache/twitter_sections/cache")  
config.twitter_section_cache_timeout = 15.minutes

config.sections[:default] += [Sections::TwitterSection]