Anatomy of a facebook wall post

This is long overdue, and probably an image that facebook should have in their docs. Here’s exactly how a wall post is structured:

as an aside you are discouraged from using the message field unless the content is user generated.


Safely overriding ActiveRecord and ActiveModel implementations of method_missing

This is really just a condensed version of yesterday’s post, with a little more emphasis on implementation than actual function.

I found way too many posts saying that you cannot or should not overrride ActiveRecord/ActiveModel implementations of method_missing. So here’s the quick and easy way to do it without losing any functionality.

def method_missing(meth, *args, &block)
    begin
        #send methods to superclass if possible.
        super
    rescue
        begin
            #custom implementation goes here.
        rescue
            # this is a workaround so it won't get stuck in a busy loop.
            raise "NoMethodError"
        end
    end
end

Overriding ActiveModel::missing_method to turn facebook into a pseudo model

This morning I started writing an app that requires facebook access. I have done this several times over (mostly during my time at gtrot) but today I came up with something I really like. It does everything you would expect, cool syntax included. I assume the same strategy would work with ActiveRecord but I am not 100% sure about that just yet.

my_facebook_account = FacebookUser.new({:access_token=>"...", :fbid=>"..."})
#accessing my own attributes:
my_friends = my_facebook_account.friends
my_checkins = my_facebook_account.checkins
#accessing my friends information just requires that you change the fbid, and continue to use the same access_token
my_friends_account = FacebookUser.new({:access_token=>"...same as above...", :fbid=>"...friends id goes here.."})
my_friends_account.checkins

As with everything else on the site this code is licensed only for non-commercial use only, if you would like to use this code commercially get in touch with me.

And now for the class:

Fair warning: if you edit this class and end up causing an exception (say, NoMethodError) you could end up stuck in a busy loop.

class FacebookUser
 
  # Includes for active model
  include ActiveModel::Validations
  include ActiveModel::Conversion
  extend ActiveModel::Naming
 
  # http get/post
  require 'net/http'
  require 'uri'
 
  attr_accessor :access_token, :fbid
 
  validates_presence_of :access_token, :fbid
 
  # class constant for timeouts
  TIMEOUT = 30
  # class constants for the facebook api root
  FB_ROOT = "http://graph.facebook.com"
  FB_ROOT_S = "https://graph.facebook.com"
 
  def initialize(attributes = {})
    attributes.each do |key, value|
      send("#{key}=", value)
    end
  end
 
  # required for active model support
  def persisted?
    false
  end
 
  # override ActiveModel::method_missing without losing functionality
  def method_missing(meth, *args, &block)
    begin
      super
    rescue
      response = https_with_token "#{FB_ROOT_S}/#{fbid}/#{meth}"
      unless response["data"].nil?
        response["data"]
      else
        response
      end
    end
  end
 
  # handle user endpoint
  def me
    https_with_token "#{FB_ROOT_S}/#{fbid}"
  end
 
  protected
 
  def https_with_token url
    do_http "#{url}?access_token=#{access_token}"
  end
 
  def http_without_token url
    do_http "#{url}"
  end
 
  def do_http url
    use_ssl = false
    if url.start_with? "https:"
      use_ssl = true
    end
    uri = URI.parse "#{url}"
    http = Net::HTTP.new uri.host, uri.port
    http.open_timeout = TIMEOUT
    http.read_timeout = TIMEOUT
    if url.start_with? "https:"
      http.use_ssl = true
      http.ssl_timeout = TIMEOUT
    end
    # facebook will return content gzipped most of the time
    request = Net::HTTP::Get.new(uri.request_uri, { "accept-encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" })
    response = http.request(request)
    # exception handling in case facebook doesn't responding with gzip
    begin
      response.body = Zlib::GzipReader.new(StringIO.new(response.body)).read
    rescue
      # nothing to do here, if that fails the response isn't gzipped or is malformed.
    end
    ActiveSupport::JSON.decode response.body
  end
end

Uncached operations with rails 3 and active record

ActiveRecord::Base.uncached do
   #all operations here will not use the cache.
end 

TyPhighter 0.0.4

TyPhighter 0.0.4 has been released, it can be found on Github

require "TyPhighter/version"
 
module TyPhighter
  class TyPhighter
    require 'net/http'
    require 'uri'
    require 'thread'
 
    @running_threads = nil
    @finished_threads = nil
    @results = nil
 
 
    def self.test_method
      this_object = TyPhighter.new
      request_objects = [{:url => "http://www.google.com/"},{:url => "http://www.yahoo.com"},{:url => "http://www.bing.com"}]
      params = {}
      params = request_objects
      this_object.new_threaded_http params
    end
 
    def initialize
      @running_threads = ThreadGroup.new
      @finished_threads = ThreadGroup.new
    end
 
    ##
    # Required params:
    # :request_objects - contains a linear array of request objects:
    # =>  [{ :url => "http://www.google.com", :post_args => {"key" => "value"} }, {:url => "http://www.yahoo.com", :post_args => {"key" => "value"}, :options => {:timeout => 5} }]
    # =>  
    #
    # Optional params:
    # :blocking - true or false, if false the request will not block
    # :port
    # :timeout - Defaults to 10 seconds
    # :headers - Defaults to empty
    # :ssl_verify - Defaults to true
    ##/
    def new_threaded_http params
      params = check_params params
      new_threads = []
      semaphore = Mutex.new
      results = {}
      params.each do |request_object|
        new_threads << Thread.new do
          this_thread = Thread.current
          puts request_object.to_s
          if request_object[:url].start_with? "https"
            use_ssl = true
          else
            use_ssl = false
          end
          this_thread[:uri] = URI.parse(request_object[:url])
          this_thread[:http] = Net::HTTP.new(this_thread[:uri].host, this_thread[:uri].port)
          this_thread[:http].use_ssl = use_ssl
          this_thread[:http].open_timeout = request_object[:timeout]
          this_thread[:http].read_timeout = request_object[:timeout]
          this_thread[:blocking] = request_object[:blocking]
          if use_ssl == true
            this_thread[:http].ssl_timeout = request_object[:timeout]
          end
          if request_object[:post_args].nil?
            if request_object[:options][:headers].nil?
              this_thread[:request] = Net::HTTP::Get.new(this_thread['uri'].request_uri)
            else
              this_thread[:request] = Net::HTTP::Get.new(this_thread['uri'].request_uri, request_object[:options][:headers])
            end
          else
            if request_object[:options][:headers].nil?
              this_thread[:request] = Net::HTTP::Post.new(this_thread['uri'].request_uri)
            else
              this_thread[:request] = Net::HTTP::Post.new(this_thread['uri'].request_uri, request_object[:options][:headers])
            end
            this_thread[:request].set_form_data(request_object[:post_args])
          end
          #warn "\nMaking request: " + this_thread[:request].to_s
          this_thread[:response] = this_thread[:http].request(this_thread[:request])
          return_hash = {}
          return_hash[:body] = this_thread[:response].body
          semaphore.synchronize {
            results[request_object[:url]] = return_hash[:body]
          }
          if request_object[:options][:blocking] == true
            this_thread.join
          end
        end
      end
      new_threads.each do |thread|
        if thread.alive?
          @running_threads.add(thread)
        else
          warn "Thread finished: " + thread.to_s
          @finished_threads.add(thread)
        end
      end
      @running_threads.list.each do |thread|
        if thread.alive?
          thread.join
        end
      end
      #results.each do |k,v|
      #  puts "key: " + k[0,50]
      #  puts "value: " + v[0,50]
      #end
      results
    end
 
    ##
    # Returns true if all threads have completed, false otherwise
    ##
    def threads_complete
      @threads.each do |thread|
        if thread.alive?
          return true
        end
      end
      return false
    end
 
    def get_data
 
    end
 
    def block_and_wait_for_threads
 
    end
 
    private
 
    def check_params params
      #puts params[:request_objects]
      if params.nil?
        raise "Must pass params"
      end
 
      unless params.kind_of? Array
        raise "params must be an array."
      else
        params.each do |request_object|
          request_object = check_request_object request_object
        end
      end
      return params
    end
 
    def check_request_object request_object
      unless request_object.kind_of? Hash
        raise "request objects must be hash: " + request_object.to_s
      end
 
      if request_object[:options].nil?
        request_object[:options] = {}
        warn "Failed to pass options for: " + request_object.to_s
      end
 
      if request_object[:url].end_with? "/"
        warn "url should not contain trailing slash(/): " + request_object.to_s
      end
 
      if request_object[:timeout].nil?
        request_object[:options][:timeout] = 10
        warn "Failed to pass params[:timeout], defaulting to 10 seconds."
      end
      return request_object
    end
 
 
  end
end