0.0
No commit activity in last 3 years
No release in over 3 years
A framework for aggregating (public) social activity into a single polymorphic persistent structure.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

Activity Mapper

A framework for aggregating (public) social activity into a single polymorphic persistent structure. Using a unified map, it uses service modules to map XML/JSON data onto a unified object space.

Included service modules:

  • Twitter
  • Delicious
  • Flickr
  • Youtube
  • Wakoopa

Step 1: Implement your Models


  # For this example, we're using OpenStruct as a storage system.
  # Normally one would use DataMapper or ActiveRecord

  require 'ostruct'

  class PersistentObject < OpenStruct

    attr_accessor :id

    def self.create(*arg)
      self.new(*arg)
    end

    def update_attributes(attrs)
      attrs.each do |name, value|
        self.send("#{name}=", value)
      end
    end

  end

  # -- All objects below need to be defined in order for activity_mapper to function

  # Your base user object
  class User < PersistentObject; end

  # Profile information: username, native_id, url
  class ServiceProfile < PersistentObject

    # belongs_to :user, implement me!
    # has_many :activities, implement me!
    def initialize(*arg); super(*arg); @activities = []; end
    attr_accessor :activities

    # -- All below is code necessary for routing to the proper service module (based on URL)

    def create_or_update_summary!(*arg); service_module ? service_module.create_or_update_summary!(*arg) : nil; end
    def aggregate_activity!(*arg); service_module ? service_module.aggregate_activity!(*arg) : nil; end
    def analyze_this(*arg); service_module ? service_module.analyze_this(*arg) : nil; end

    def service_module
      return @service_module if @service_module
      if (service_module_klass = ActivityMapper::ServiceModule.klass_for(url))
        @service_module = service_module_klass.new(self)
      else
        nil
      end
    end

  end

  # The actual event that happened: caption, occurred_at, url, reference to ActivityObject
  class Activity < PersistentObject

    # has_one :object, implement me!

    # Implement anti-duplication mechanisms here
    def self.exists?(user_id, entry)
      false
    end

  end

  # The object that's referenced by the event: title, body, url, created_at
  class ActivityObject < PersistentObject

    def self.fetch(content_identifier, activity_object_type_id)
      nil
    end

    def self.content_identifier(url)
      MD5.hexdigest(url)
    end

    # To support space separated tags from APIs
    def spaced_tags=(value)
      self.tag_list = value.to_s.split(' ')
    end

  end

  # Optional object that holds ranking/stats information
  class RatingSummary < PersistentObject; end

  # Hold media information
  class Media < PersistentObject; end

  # See activitystrea.ms for more verbs
  class ActivityVerb < PersistentObject

    # Constant Cache
    POST          = ActivityVerb.new(:id => 1)
    FAVORITE      = ActivityVerb.new(:id => 2)
    RECENTLY_USED = ActivityVerb.new(:id => 3)
    NEWLY_USED    = ActivityVerb.new(:id => 4)

  end

  # See activitystrea.ms for more types
  class ActivityObjectType < PersistentObject

    # Constant Cache
    STATUS    = ActivityObjectType.new(:id => 1)
    BOOKMARK  = ActivityObjectType.new(:id => 2)
    PHOTO     = ActivityObjectType.new(:id => 3)
    VIDEO     = ActivityObjectType.new(:id => 4)
    SONG      = ActivityObjectType.new(:id => 5)
    MIXED     = ActivityObjectType.new(:id => 6)
    SOFTWARE  = ActivityObjectType.new(:id => 7)
    SLIDESHOW = ActivityObjectType.new(:id => 8)

  end

Step 2: Map that activity!


  require 'rubygems'
  require 'activity_mapper'
  
  profile = ServiceProfile.create(:url => 'http://twitter.com/dominiek')
  # Gather the most basic credentials:
  profile.create_or_update_summary! 
  # => profile.username
  # => profile.native_id
  # => profile.url
  
  
  profile.aggregate_activity!
  activity = profile.activities.first
  # => activity.caption
  # => activity.occured_at
  # => activity.native_id
  # => activity.object.title
  # => activity.object.body
  # => activity.object.native_id

Example Service Module


  module ActivityMapper

    class TwitterServiceModule < ServiceModule
      ACTIVITY_MAP = {
        nil => {
          'activity.occurred_at'      => 'created_at',
          'activity.native_id'        => 'id',
          'activity_object.native_id' => 'id',
          'activity.caption'          => 'text',
          'activity_object.title'     => 'text',
          'activity_object.body'      => 'text'
        }
      }
      ACCEPTED_HOSTS = [/twitter\.com/]

      def create_or_update_summary!(options = {})
        attributes = {}
        attributes[:username] = @profile.username || self.class.username_from_url(@profile.url)

        mapper = ActivityDataMapper.new(ACTIVITY_MAP)
        mapper.fetch!("http://twitter.com/statuses/user_timeline/#{attributes[:username]}.json", :format => :json)
        tweets = mapper.data

        attributes[:avatar_url] = tweets.blank? ? nil : tweets.first['user']['profile_image_url']
        attributes[:native_user_id] = tweets.first['user']['id'].to_i
        @profile.update_attributes(attributes)
      end

      def aggregate_activity!(options = {})
        mapper = ActivityDataMapper.new(ACTIVITY_MAP)
        mapper.fetch!("http://twitter.com/statuses/user_timeline/#{@profile.username}.json", :format => :json)
        mapper.map!
        mapper.entries.sort! { |e2,e1|
          e1['activity.occurred_at'] <=> e2['activity.occurred_at']
        }
        mapper.entries.each do |entry|
          entry['activity_object.url'] = entry['activity.url'] = "http://twitter.com/#{@profile.username}/status/#{entry['activity.native_id']}"
          break if Activity.exists?(@profile.user_id, entry)
          create_activity(entry, ActivityObjectType::STATUS, ActivityVerb::POST) do |activity, activity_object|
          end

        end
      end

    end
  end

Author

Dominiek ter Heide
http://dominiek.com/
(Note: I wrote this a while back and thought this could be useful to some developers)