sparkapi/spark_api

View on GitHub
lib/spark_api/models/listing.rb

Summary

Maintainability
C
1 day
Test Coverage
module SparkApi
  module Models
    class Listing < Base 
      extend Finders
      attr_accessor :photos, :videos, :virtual_tours, :documents, :open_houses, :tour_of_homes, :rental_calendars, :floplans
      attr_accessor :constraints
      self.element_name="listings"
      DATA_MASK = "********"
      WRITEABLE_FIELDS = ["ListPrice", "ExpirationDate"]

      def initialize(attributes={})
        @photos = []
        @videos = []
        @virtual_tours = []
        @rental_calendars = []
        @documents = []
        @constraints = []
        @tour_of_homes = []
        @open_houses = []
        @floplans = []

        if attributes.has_key?('StandardFields')
          pics, vids, tours, docs, ohouses, tourhomes = attributes['StandardFields'].values_at('Photos','Videos', 'VirtualTours', 'Documents', 'OpenHouses', 'TourOfHomes')
        end

        if attributes.has_key?('RentalCalendar')
          rentalcalendars = attributes['RentalCalendar']
        end

        if attributes.has_key?('FloPlans')
          floplans = attributes['FloPlans']
        end

        if pics != nil
          setup_attribute(@photos, pics, Photo)
          attributes['StandardFields'].delete('Photos')
        end

        if vids != nil
          setup_attribute(@videos, vids, Video)
          attributes['StandardFields'].delete('Videos')
        end

        if tours != nil
          setup_attribute(@virtual_tours, tours, VirtualTour)
          attributes['StandardFields'].delete('VirtualTours')
        end

        if docs != nil
          setup_attribute(@documents, docs, Document)
          attributes['StandardFields'].delete('Documents')
        end

        if ohouses != nil
          setup_attribute(@open_houses, ohouses, OpenHouse)
          attributes['StandardFields'].delete('OpenHouses')
        end

        if tourhomes != nil
          setup_attribute(@tour_of_homes, tourhomes, TourOfHome)
          attributes['StandardFields'].delete('TourOfHomes')
        end

        if rentalcalendars != nil
          setup_attribute(@rental_calendars, rentalcalendars, RentalCalendar)
          attributes.delete('RentalCalendar')
        end

        if floplans != nil
          setup_attribute(@floplans, floplans, FloPlan)
          attributes.delete('FloPlans')
        end

        super(attributes)
      end

      def self.find_by_cart_id(cart_id, options={}) 
        query = {:_filter => "ListingCart Eq '#{cart_id}'"}
        find(:all, options.merge(query)) 
      end
      
      def self.my(arguments={})
        collect(connection.get("/my/listings", arguments))
      end

      def self.office(arguments={})
        collect(connection.get("/office/listings", arguments))
      end

      def self.company(arguments={})
        collect(connection.get("/company/listings", arguments))
      end
      
      def self.nearby(latitude, longitude, arguments={})
        nearby_args = {:_lat => latitude, :_lon => longitude}.merge(arguments)
        collect(connection.get("/listings/nearby", nearby_args))
      end

      def self.tour_of_homes(arguments={})
        collect(connection.get("/listings/tourofhomes", arguments))
      end
      
      def tour_of_homes(arguments={})
        @tour_of_homes ||= TourOfHome.find_by_listing_key(self.Id, arguments)
        return @tour_of_homes unless @tour_of_homes.nil?
      end

      def rental_calendars(arguments={})
        @rental_calendars ||= RentalCalendar.find_by_listing_key(self.Id, arguments)
        return @rental_calendars unless @rental_calendars.nil?
      end


      def open_houses(arguments={})
        @open_houses ||= OpenHouse.find_by_listing_key(self.Id, arguments)
        return @open_houses unless @open_houses.nil?
      end

      def my_notes
        Note.build_subclass.tap do |note|
          note.prefix = "/listings/#{self.ListingKey}"
          note.element_name = "/my/notes"
        end
      end

      # 'fore' is required when accessing an agent's shared
      # notes for a specific contact. If the ApiUser /is/ the
      # contact, then it can be inferred by the api, so it's
      # unecessary
      def shared_notes(fore=nil)
        Note.build_subclass.tap do |note|
          note.prefix = "/listings/#{self.ListingKey}"
          if fore.nil?
            note.element_name = "/shared/notes"
          else
            note.element_name = "/shared/notes/contacts/#{fore}"
          end
        end
      end

      def street_address        
        (self.UnparsedFirstLineAddress || '').delete(DATA_MASK).strip().gsub(/\s{2,}/, ' ')
      end

      def region_address
        "#{self.City}, #{self.StateOrProvince} #{self.PostalCode}".delete(DATA_MASK).strip().gsub(/^,\s/, '').gsub(/,$/, '')
      end

      def full_address
        "#{self.street_address}, #{self.region_address}".strip().gsub(/^,\s/, '').gsub(/,$/, '')
      end
      
      def save(arguments={})
        self.errors = []
        begin
          return save!(arguments)
        rescue BadResourceRequest => e
          self.errors << {:code => e.code, :message => e.message}
          if e.code == 1053
            @constraints = []
            e.details.each do |detail|
              detail.each_pair do |k,v|
                v.each { |constraint| @constraints << Constraint.new(constraint)}
              end
            end
          end
          SparkApi.logger.warn { "Failed to save resource #{self}: #{e.message}" }
        rescue NotFound => e
          SparkApi.logger.error { "Failed to save resource #{self}: #{e.message}" }
        end
        false
      end

      def save!(arguments={})
        writable_changed_keys = changed & WRITEABLE_FIELDS
        if writable_changed_keys.empty?
          SparkApi.logger.warn { "No supported listing change detected" }
        else
          results = connection.put "/flexmls#{self.class.path}/#{self.Id}", build_hash(writable_changed_keys), arguments
          @contstraints = []
          results.details.each do |detail|
            detail.each_pair do |k,v|
              v.each { |constraint| @constraints << Constraint.new(constraint)}
            end
          end
        end
        true
      end

      def reorder_photos(arguments={})
        begin
          return reorder_photos!(arguments)
        rescue BadResourceRequest => e
          SparkApi.logger.warn { "Failed to save resource #{self}: #{e.message}" }
        rescue NotFound => e
          SparkApi.logger.error { "Failed to save resource #{self}: #{e.message}" }
        end
        false
      end
      def reorder_photos!(arguments={})
        results = connection.put subresource_path("photos"), arguments
        true
      end

      def reorder_photo(photo_id, index)
        unless Integer(index)
          raise ArgumentError, "Photo reorder failed. '#{index}' is not a number."
        end

        begin
          return reorder_photo!(photo_id, index)
        rescue BadResourceRequest => e
          SparkApi.logger.warn { "Failed to save resource #{self}: #{e.message}" }
        rescue NotFound => e
          SparkApi.logger.error { "Failed to save resource #{self}: #{e.message}" }
        end
        false
      end
      def reorder_photo!(photo_id, index)
        connection.put subresource_path("photos") + "/#{photo_id}", "Photos" => [{"Order"=>index}]
        true
      end

      def editable?(editable_settings = [])
        settings = Array(editable_settings)
        editable = attributes.include?("Permissions") && self.Permissions["Editable"] == true
        if editable
          settings.each{ |setting| editable = false unless self.Permissions["EditableSettings"][setting.to_s] == true }
        end
        editable
      end

      def ExpirationDate
        attributes["ExpirationDate"]
      end
      def ExpirationDate=(value)
        write_attribute("ExpirationDate", value)
      end

      def respond_to?(method_symbol, include_all=false)
        if super
          true
        else
          attributes['StandardFields'].include?(method_symbol.to_s) rescue false
        end
      end

      def delete_photos!(photoIds, args={})
        connection.delete subresource_path("photos") + "/#{photoIds}", args
      end

      def delete_photos(photoIds, args={})
        unless photoIds.is_a? String
          raise ArgumentError, "Batch photo delete failed. '#{photoIds}' is not a string."
        end

        begin
          return delete_photos!(photoIds, args)
        rescue BadResourceRequest => e
          SparkApi.logger.warn { "Failed to delete photos from resource #{self}: #{e.message}" }
        rescue NotFound => e
          SparkApi.logger.error { "Failed to delete photos from resource #{self}: #{e.message}" }
        end
        false
      end

      private

      # TODO trim this down so we're only overriding the StandardFields access
      def method_missing(method_symbol, *arguments)
        method_name = method_symbol.to_s

        if method_name =~ /(=|\?)$/
          case $1
          when "="
            write_attribute($`,arguments.first)
            # TODO figure out a nice way to present setters for the standard fields
          when "?"
            attributes[$`]
          end  
        else 
          return attributes[method_name] if attributes.include?(method_name)
          return @attributes['StandardFields'][method_name] if attributes['StandardFields'].include?(method_name)
          super
        end
      end

      def build_hash(keys)
        hash = {}
        keys.each do |key|
          hash[key] = attributes[key]
        end
        hash
      end

      # Determine if passed a model or hash and push instance of Klass onto attributes array
      def setup_attribute(attributes, collection, klass)
        collection.collect do |c|
          attribute = (c.instance_of? klass) ? c : klass.new(c)
          attributes.push(attribute)
        end
      end

      def subresource_path(subresource)
        "#{self.class.path}/#{self.Id}/#{subresource}"
      end

    end
  end
end