lardawge/rfm

View on GitHub
lib/rfm/resultset.rb

Summary

Maintainability
A
0 mins
Test Coverage
# This module includes classes that represent FileMaker data. When you communicate with FileMaker
# using, ie, the Layout object, you typically get back ResultSet objects. These contain Records,
# which in turn contain Fields, Portals, and arrays of data.
#
# Author::    Geoff Coffey  (mailto:gwcoffey@gmail.com)
# Copyright:: Copyright (c) 2007 Six Fried Rice, LLC and Mufaddal Khumri
# License::   See MIT-LICENSE for details
require 'nokogiri'
require 'bigdecimal'
require 'rfm/record'
require 'rfm/metadata/field'

module Rfm

  # The ResultSet object represents a set of records in FileMaker. It is, in every way, a real Ruby
  # Array, so everything you expect to be able to do with an Array can be done with a ResultSet as well.
  # In this case, the elements in the array are Record objects.
  #
  # Here's a typical example, displaying the results of a Find:
  #
  #   myServer = Rfm::Server.new(...)
  #   results = myServer["Customers"]["Details"].find("First Name" => "Bill")
  #   results.each {|record|
  #     puts record["First Name"]
  #     puts record["Last Name"]
  #     puts record["Email Address"]
  #   }
  #
  # =Attributes
  #
  # The ResultSet object has these attributes:
  #
  # * *field_meta* is a hash with field names for keys and Field objects for values; it provides 
  #   info about the fields in the ResultSet
  #
  # * *portal_meta* is a hash with table occurrence names for keys and arrays of Field objects for values;
  #   it provides metadata about the portals in the ResultSet and the Fields on those portals

  class Resultset < Array
    
    attr_reader :layout, :server
    attr_reader :field_meta, :portal_meta
    attr_reader :date_format, :time_format, :timestamp_format
    attr_reader :total_count, :foundset_count
    
    # Initializes a new ResultSet object. You will probably never do this your self (instead, use the Layout
    # object to get various ResultSet obejects).
    #
    # If you feel so inclined, though, pass a Server object, and some +fmpxmlresult+ compliant XML in a String.
    #
    # =Attributes
    #
    # The ResultSet object includes several useful attributes:
    #
    # * *fields* is a hash (with field names for keys and Field objects for values). It includes an entry for
    #   every field in the ResultSet. Note: You don't use Field objects to access _data_. If you're after 
    #   data, get a Record object (ResultSet is an array of records). Field objects tell you about the fields
    #   (their type, repetitions, and so forth) in case you find that information useful programmatically.
    #
    #   Note: keys in the +fields+ hash are downcased for convenience (and [] automatically downcases on 
    #   lookup, so it should be seamless). But if you +each+ a field hash and need to know a field's real
    #   name, with correct case, do +myField.name+ instead of relying on the key in the hash.
    #
    # * *portals* is a hash (with table occurrence names for keys and Field objects for values). If your
    #   layout contains portals, you can find out what fields they contain here. Again, if it's the data you're
    #   after, you want to look at the Record object.
    
    def initialize(server, xml_response, layout, portals=nil)
      @layout           = layout
      @server           = server
      @field_meta     ||= Rfm::CaseInsensitiveHash.new
      @portal_meta    ||= Rfm::CaseInsensitiveHash.new
      @include_portals  = portals 
      
      doc = Nokogiri.XML(remove_namespace(xml_response))
      
      error = doc.xpath('/fmresultset/error').attribute('code').value.to_i
      check_for_errors(error, server.state[:raise_on_401])

      datasource        = doc.xpath('/fmresultset/datasource')
      meta              = doc.xpath('/fmresultset/metadata')
      resultset         = doc.xpath('/fmresultset/resultset')

      @date_format      = convert_date_time_format(datasource.attribute('date-format').value)
      @time_format      = convert_date_time_format(datasource.attribute('time-format').value)
      @timestamp_format = convert_date_time_format(datasource.attribute('timestamp-format').value)

      @foundset_count   = resultset.attribute('count').value.to_i
      @total_count      = datasource.attribute('total-count').value.to_i

      parse_fields(meta)
      parse_portals(meta) if @include_portals
      
      Rfm::Record.build_records(resultset.xpath('record'), self, @field_meta, @layout)
      
    end
    
    private
      def remove_namespace(xml)
        xml.gsub('xmlns="http://www.filemaker.com/xml/fmresultset" version="1.0"', '')
      end
    
      def check_for_errors(code, raise_401)
        raise Rfm::Error.getError(code) if code != 0 && (code != 401 || raise_401)
      end
    
      def parse_fields(meta)
        meta.xpath('field-definition').each do |field|
          @field_meta[field['name']] = Rfm::Metadata::Field.new(field)
        end
      end

      def parse_portals(meta)
        meta.xpath('relatedset-definition').each do |relatedset|
          table, fields = relatedset.attribute('table').value, {}

          relatedset.xpath('field-definition').each do |field|
            name = field.attribute('name').value.gsub(Regexp.new(table + '::'), '')
            fields[name] = Rfm::Metadata::Field.new(field)
          end

          @portal_meta[table] = fields
        end
      end
    
      def convert_date_time_format(fm_format)
        fm_format.gsub!('MM', '%m')
        fm_format.gsub!('dd', '%d')
        fm_format.gsub!('yyyy', '%Y')
        fm_format.gsub!('HH', '%H')
        fm_format.gsub!('mm', '%M')
        fm_format.gsub!('ss', '%S')
        fm_format
      end
    
  end
end