Making Twitter mobile client with Ruby using Rhodes Rhosync (Part 3)

21Feb09

Previously

At the previous 2 posts, we went through the very basic of how you write code in Rhosync and Rhodes.

This time, we go through the similar steps,but additional functionalities to read and create your own tweet.

Creating New source at Rhosync

Let’s first create new source, just in the same way as you created the public timeline at Part 1, but new name as “MyTimeline”

  $ rhogen source MyTimeline
  Generating with source generator:
       [ADDED]  lib/my_timeline.rb

Authentication and My Tweet.

I initially thought I have to implement some authentication logic at “login” method, but realized that none of REST based examples (Basecamp and Lighthouse) have logic at login method.

Instead, it uses Net::HTTP’s authentication mechanism within query method.
Here is an example of Basecamp adapter.

  def login
    # intentionally left blank
  end

  def query
    uri = URI.parse(@source.url+"/projects.xml")
    req = Net::HTTP::Get.new(uri.path, 'Accept' => 'application/xml')
    req.basic_auth @source.login, @source.password
    response = Net::HTTP.start(uri.host,uri.port) do |http|
      http.request(req)
    end
    xml_data = XmlSimple.xml_in(response.body); 
    @result = xml_data["project"]
  end

According to Net::HTTP rdoc, you can call “basic_auth” instance method with login and password parameter.

The below is query method for MyTimeline. It’s pretty much identical. Only the differences are url and response parameter.

Do you remember that you can access url info you added at Rhosync Web interface through @source? Both login and password are also available through it, so you don’t need to hardcode at the source code.

The rest (sync and other private methods) are exactly same as PublicTimeline

  def query
    url = "/statuses/friends_timeline.xml"
    uri = URI.parse(@source.url+url)

    req = Net::HTTP::Get.new(uri.path, 'Accept' => 'application/xml')
    req.basic_auth @source.login, @source.password

    res = Net::HTTP.start(uri.host,uri.port) do |http|
      http.request(req)
    end

    xml_data = XmlSimple.xml_in(res.body); 
    @result = xml_data["status"]
  end

  def sync
    log "#{self.class} sync, with #{@result.length} results"
    @result.each do |item|
      item_id = item["id"].first.to_i
      iterate_keys(:item => item, :item_id => item_id)
    end
  end

private
  def iterate_keys(option)
    item = option[:item]
    item_id = option[:item_id]
    prefix = option[:prefix] || ""

    # item.keys => ["user", "favorited", "truncated"...]
    item.keys.each do |key|
      value = item[key] ? item[key][0] : ""
      if value.kind_of?(Hash) && value != {}
        # eg. :user => {:url => 'foo} becomes user_url
        iterate_keys(:prefix => (prefix + underline(key) + "_"), :item => value, :item_id => item_id)
      else
        # This method is from rest_api_helper
        add_triple(@source.id, item_id, prefix + underline(key), value)
      end
    end
  end

  def underline(key)
    key.gsub('-','_')
  end

Let’s start up the server and add the source as you did for PublicTimeline, plus actual login and password of your twitter account. If you click “Show Records”, you should be able to see your friend’s tweets.

Creating Tweet

Create method does not have a lot of examples at sample apps. Most of REST adapters (BaseCamp and Lighthouse) do not have create method for some reason.

I finally found that “lighthouse_tickets.rb” has some examples.

  def create(name_value_list)
    log "LighthouseTickets create"

    get_params(name_value_list)
    xml_str = xml_template(params)

    uri = URI.parse(base_url)
    Net::HTTP.start(uri.host) do |http|
      http.set_debug_output $stderr
      request = Net::HTTP::Post.new(uri.path + "/projects/#{params['project_id']}/tickets.xml", {'Content-type' => 'application/xml'})
      request.body = xml_str
      request.basic_auth @source.credential.token, "x"
      response = http.request(request)
      # log response.body

      # case response
      # when Net::HTTPSuccess, Net::HTTPRedirection
      #   # OK
      # else
      #   raise "Failed to create  ticket"
      # end
    end
  end

What they are doing here is passing name_value_list into requrest.body in xml format. Looks get_params method does all conversion work. Let’s look at the actual method within RestAPIHelpers

  module RestAPIHelpers
    # convert name_value_list to a params hash
    # name_value_list example, "[{'name' => 'title', 'value' => 'testing'},{'name' => 'state', 'value' => 'new'}]"
    # => params['title'] = 'testing', etc.
    def get_params(name_value_list)
      @params = {}
      name_value_list.each do |pair| 
        @params.merge!(Hash[pair['name'], pair['value']])
      end
      log @params.inspect
    end

Ah, here is a useful comment.
The comment says that get_params iterates through each array and converts {‘name’ => ‘title’, ‘value’ => ‘testing’} hash into {‘title’ => ‘testing’} hash and set the result into @params instance variable. If you see further bottom of the RestAPIHelpers, you can see “params” method which simply returns @params

  def params
    @params
  end

The below is how I implemented the create method for MyTimeline.

  def create(name_value_list)
    url = "/statuses/update.xml"
    uri = URI.parse(@source.url+url)

    get_params(name_value_list)

    req = Net::HTTP::Post.new(uri.path, 'Accept' => 'application/xml')
    req.basic_auth @source.login, @source.password

    req.set_form_data('status'=> params["text"])

    res = Net::HTTP.start(uri.host,uri.port) do |http|
      http.request(req)
    end
  end

This is very similar to “query” method, but here are the few differences.

  • Specified url as “update.xml”
  • Net::HTTP::Post.new. Note this is Post class, not Get class.
  • Passed text value using set_form_data method in Hash format. This way, you don’t have to convert your data into xml format and you can post to either “update.xml” or “update.json” url

Final Ryoshnc code

  class MyTimeline < SourceAdapter

    include RestAPIHelpers

    def initialize(source, credential = nil)
      super
    end

    # Looks not in use
    # def login
    #   
    # end

    def query
      url = "/statuses/friends_timeline.xml"
      uri = URI.parse(@source.url+url)

      # Login authentication. The logic is taken from basecamp_projects.rb
      req = Net::HTTP::Get.new(uri.path, 'Accept' => 'application/xml')
      req.basic_auth @source.login, @source.password

      res = Net::HTTP.start(uri.host,uri.port) do |http|
        http.request(req)
      end

      xml_data = XmlSimple.xml_in(res.body); 
      @result = xml_data["status"]
    end

    def sync
      log "#{self.class} sync, with #{@result.length} results"
      @result.each do |item|
        item_id = item["id"].first.to_i
        iterate_keys(:item => item, :item_id => item_id)
      end
    end

    def create(name_value_list)
      url = "/statuses/update.xml"
      uri = URI.parse(@source.url+url)

      get_params(name_value_list)

      req = Net::HTTP::Post.new(uri.path, 'Accept' => 'application/xml')
      req.basic_auth @source.login, @source.password

      req.set_form_data('status'=> params["text"])

      res = Net::HTTP.start(uri.host,uri.port) do |http|
        http.request(req)
      end
    end

  private
    def iterate_keys(option)
      item = option[:item]
      item_id = option[:item_id]
      prefix = option[:prefix] || ""

      # item.keys => ["user", "favorited", "truncated"...]
      item.keys.each do |key|
        value = item[key] ? item[key][0] : ""
        if value.kind_of?(Hash) && value != {}
          # eg. :user => {:url => 'foo} becomes user_url
          iterate_keys(:prefix => (prefix + underline(key) + "_"), :item => value, :item_id => item_id)
        else
          # This method is from rest_api_helper
          add_triple(@source.id, item_id, prefix + underline(key), value, @source.current_user.id)
        end
      end
    end

    def underline(key)
      key.gsub('-','_')
    end
  end

Demo

Let’s go back to Rhosync server. You should have left it when you displayed your friends tweet. Click “Create object-value record”, and add “text” attribute and your own tweet message like below.

Once you click “Create Object Value”, then you should be redirected to show page with “Created Object” flash message.

Now you go to your Twitter website. Congratulations!! Your message from Rhosync is successfully updated into Twitter itself!!

Next

You can finally post your own post from iPhone simulator. I will also go through some technics to tidy up your view pages.

Advertisements


No Responses Yet to “Making Twitter mobile client with Ruby using Rhodes Rhosync (Part 3)”

  1. Leave a Comment

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: