Making Twitter mobile client with Ruby using Rhodes Rhosync (Part 3)
Previously
- Part One – Syncing public Tweet on Rhosync server
- Part Two – Displaying public Tweet on iPhone Simulator
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.
Filed under: Rhodes | Leave a Comment
Tags: tutorial



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