# Contains Twitter4R Model API. module Twitter # Mixin module for model classes. Includes generic class methods like # unmarshal. # # To create a new model that includes this mixin's features simply: # class NewModel # include Twitter::ModelMixin # end # # This mixin module automatically includes Twitter::ClassUtilMixin # features. # # The contract for models to use this mixin correctly is that the class # including this mixin must provide an class method named attributes # that will return an Array of attribute symbols that will be checked # in #eql? override method. The following would be sufficient: # def self.attributes; @@ATTRIBUTES; end module ModelMixin #:nodoc: def self.included(base) #:nodoc: base.send(:include, Twitter::ClassUtilMixin) base.send(:include, InstanceMethods) base.extend(ClassMethods) end # Class methods defined for Twitter::ModelMixin module. module ClassMethods #:nodoc: # Unmarshal object singular or plural array of model objects # from JSON serialization. Currently JSON is only supported # since this is all Twitter4R needs. def unmarshal(raw) input = JSON.parse(raw) def unmarshal_model(hash) self.new(hash) end return unmarshal_model(input) if input.is_a?(Hash) # singular case result = [] input.each do |hash| model = unmarshal_model(hash) if hash.is_a?(Hash) result << model end if input.is_a?(Array) result # plural case end end # Instance methods defined for Twitter::ModelMixin module. module InstanceMethods #:nodoc: attr_accessor :client # Equality method override of Object#eql? default. # # Relies on the class using this mixin to provide a attributes # class method that will return an Array of attributes to check are # equivalent in this #eql? override. # # It is by design that the #eql? method will raise a NoMethodError # if no attributes class method exists, to alert you that # you must provide it for a meaningful result from this #eql? override. # Otherwise this will return a meaningless result. def eql?(other) attrs = self.class.attributes attrs.each do |att| return false unless self.send(att).eql?(other.send(att)) end true end # Returns integer representation of model object instance. # # For example, # status = Twitter::Status.new(:id => 234343) # status.to_i #=> 234343 def to_i @id end # Returns string representation of model object instance. # # For example, # status = Twitter::Status.new(:text => 'my status message') # status.to_s #=> 'my status message' # # If a model class doesn't have a @text attribute defined # the default Object#to_s will be returned as the result. def to_s self.respond_to?(:text) ? @text : super.to_s end # Returns hash representation of model object instance. # # For example, # u = Twitter::User.new(:id => 2342342, :screen_name => 'tony_blair_is_the_devil') # u.to_hash #=> {:id => 2342342, :screen_name => 'tony_blair_is_the_devil'} # # This method also requires that the class method attributes be # defined to return an Array of attributes for the class. def to_hash attrs = self.class.attributes result = {} attrs.each do |att| value = self.send(att) value = value.to_hash if value.respond_to?(:to_hash) result[att] = value if value end result end # "Blesses" model object. # # Should be overridden by model class if special behavior is expected # # Expected to return blessed object (usually self) def bless(client) self.basic_bless(client) end protected # Basic "blessing" of model object def basic_bless(client) self.client = client self end end end module AuthenticatedUserMixin def self.included(base) base.send(:include, InstanceMethods) end module InstanceMethods # Returns an Array of user objects that represents the authenticated # user's friends on Twitter. def followers(options = {}) @client.my(:followers, options) end # Adds given user as a friend. Returns user object as given by # Twitter REST server response. # # For user argument you may pass in the unique integer # user ID, screen name or Twitter::User object representation. def befriend(user) @client.friend(:add, user) end # Removes given user as a friend. Returns user object as given by # Twitter REST server response. # # For user argument you may pass in the unique integer # user ID, screen name or Twitter::User object representation. def defriend(user) @client.friend(:remove, user) end end end # Represents a Twitter user class User include ModelMixin @@ATTRIBUTES = [:id, :name, :description, :location, :screen_name, :url, :protected, :profile_image_url, :profile_background_color, :profile_text_color, :profile_link_color, :profile_sidebar_fill_color, :profile_sidebar_border_color, :profile_background_image_url, :profile_background_tile, :utc_offset, :time_zone, :following, :notifications, :favourites_count, :followers_count, :friends_count, :statuses_count, :created_at, ] attr_accessor *@@ATTRIBUTES class << self # Used as factory method callback def attributes; @@ATTRIBUTES; end # Returns user model object with given id using the configuration # and credentials of the client object passed in. # # You can pass in either the user's unique integer ID or the user's # screen name. def find(id, client) client.user(id) end end # Override of ModelMixin#bless method. # # Adds #followers instance method when user object represents # authenticated user. Otherwise just do basic bless. # # This permits applications using Twitter4R to write # Rubyish code like this: # followers = user.followers if user.is_me? # Or: # followers = user.followers if user.respond_to?(:followers) def bless(client) basic_bless(client) self.instance_eval(%{ self.class.send(:include, Twitter::AuthenticatedUserMixin) }) if self.is_me? and not self.respond_to?(:followers) self end # Returns whether this Twitter::User model object # represents the authenticated user of the client # that blessed it. def is_me? # TODO: Determine whether we should cache this or not? # Might be dangerous to do so, but do we want to support # the edge case where this would cause a problem? i.e. # changing authenticated user after initial use of # authenticated API. # TBD: To cache or not to cache. That is the question! # Since this is an implementation detail we can leave this for # subsequent 0.2.x releases. It doesn't have to be decided before # the 0.2.0 launch. @screen_name == @client.instance_eval("@login") end # Returns an Array of user objects that represents the authenticated # user's friends on Twitter. def friends @client.user(@id, :friends) end end # User # Represents a status posted to Twitter by a Twitter user. class Status include ModelMixin @@ATTRIBUTES = [:id, :text, :source, :truncated, :created_at, :user, :favorited, :in_reply_to_status_id, :in_reply_to_user_id, :in_reply_to_screen_name] attr_accessor *@@ATTRIBUTES class << self # Used as factory method callback def attributes; @@ATTRIBUTES; end # Returns status model object with given status using the # configuration and credentials of the client object passed in. def find(id, client) client.status(:get, id) end # Creates a new status for the authenticated user of the given # client context. # # You MUST include a valid/authenticated client context # in the given params argument. # # For example: # status = Twitter::Status.create( # :text => 'I am shopping for flip flops', # :client => client) # # An ArgumentError will be raised if no valid client context # is given in the params Hash. For example, # status = Twitter::Status.create(:text => 'I am shopping for flip flops') # The above line of code will raise an ArgumentError. # # The same is true when you do not provide a :text key-value # pair in the params argument given. # # The Twitter::Status object returned after the status successfully # updates on the Twitter server side is returned from this method. def create(params) client, text = params[:client], params[:text] raise ArgumentError, 'Valid client context must be provided' unless client.is_a?(Twitter::Client) raise ArgumentError, 'Must provide text for the status to update' unless text.is_a?(String) client.status(:post, text) end end def reply? !!@in_reply_to_status_id end def reply(status) client.status(:reply, :status => status, :in_reply_to_status_id => @id) end protected # Constructor callback def init @user = User.new(@user) if @user.is_a?(Hash) @created_at = Time.parse(@created_at) if @created_at.is_a?(String) end end # Status # Represents a direct message on Twitter between Twitter users. class Message include ModelMixin @@ATTRIBUTES = [:id, :recipient, :sender, :text, :created_at] attr_accessor *@@ATTRIBUTES class << self # Used as factory method callback def attributes; @@ATTRIBUTES; end # Raises NotImplementedError because currently # Twitter doesn't provide a facility to retrieve # one message by unique ID. def find(id, client) raise NotImplementedError, 'Twitter has yet to implement a REST API for this. This is not a Twitter4R library limitation.' end # Creates a new direct message from the authenticated user of the # given client context. # # You MUST include a valid/authenticated client context # in the given params argument. # # For example: # status = Twitter::Message.create( # :text => 'I am shopping for flip flops', # :receipient => 'anotherlogin', # :client => client) # # An ArgumentError will be raised if no valid client context # is given in the params Hash. For example, # status = Twitter::Status.create(:text => 'I am shopping for flip flops') # The above line of code will raise an ArgumentError. # # The same is true when you do not provide any of the following # key-value pairs in the params argument given: # * text - the String that will be the message text to send to user # * recipient - the user ID, screen_name or Twitter::User object representation of the recipient of the direct message # # The Twitter::Message object returned after the direct message is # successfully sent on the Twitter server side is returned from # this method. def create(params) client, text, recipient = params[:client], params[:text], params[:recipient] raise ArgumentError, 'Valid client context must be given' unless client.is_a?(Twitter::Client) raise ArgumentError, 'Message text must be supplied to send direct message' unless text.is_a?(String) raise ArgumentError, 'Recipient user must be specified to send direct message' unless [Twitter::User, Integer, String].member?(recipient.class) client.message(:post, text, recipient) end end protected # Constructor callback def init @sender = User.new(@sender) if @sender.is_a?(Hash) @recipient = User.new(@recipient) if @recipient.is_a?(Hash) @created_at = Time.parse(@created_at) if @created_at.is_a?(String) end end # Message # RateLimitStatus provides information about how many requests you have left # and when you can resume more requests if your remaining_hits count is zero. class RateLimitStatus include ModelMixin @@ATTRIBUTES = [:remaining_hits, :hourly_limit, :reset_time_in_seconds, :reset_time] attr_accessor *@@ATTRIBUTES class << self # Used as factory method callback def attributes; @@ATTRIBUTES; end end end end # Twitter