require 'eloqua/api/service' require 'eloqua/remote_object' module Eloqua class Query delegate :api, :to => :remote_object attr_reader :collection, :remote_object, :conditions, :total_pages attr_internal :current_page, :query_started # The amount of time in seconds to wait before sending another request to Eloqua @@request_delay = 1 cattr_accessor :request_delay # Create a new query to attach conditions to. # # class Contact < Eloqua::Entity # remote_type = api.remote_type('Contact') # end # # Eloqua::Query.new(Contact) # # @param [Eloqua::RemoteObject] or one of its descendants def initialize(remote_object) unless(remote_object.is_a?(Class) && Eloqua::RemoteObject >= remote_object) raise(ArgumentError, 'must provide an Eloqua::RemoteObject or one of its descendants') end @page = 1 @limit = 200 @collection = [] @remote_object = remote_object @conditions = [] @fields = nil @has_requested = false end ## CHAIN-ABLES they reset the has_requested? but do not clear the collection # Sets or gets limit # # query.limit(5) # sets limit returns self # query.limit # returns 5 # # @param [Integer] # @return [self, Integer] def limit(value = nil); end # Sets or gets page # # query.page(5) # sets limit returns self # query.page # returns 5 # # @param [Integer] # @return [self, Integer] # def page(value = nil); end # Sets or gets the array of fields to find. # Can use a literal string or a symbol of a {Eloqua::RemoteObject#map mapped} attribute # # query.fields([:email, 'Date']) # query.fields # returns [:email, 'Date'] # # @see Eloqua::RemoteObject::map # @param [Array] # @return [self, Array] def fields(value = nil); end # This mess defines the limit and page getter/setter methods # when they are set they will also set #has_requested? to false [:fields, :limit, :page].each do |attr| class_eval(<<-RUBY, __FILE__, __LINE__ + 1) remove_method(:#{attr}) def #{attr}(value = nil) if(value.nil?) @#{attr} else @has_requested = false @#{attr} = value self end end RUBY end # Clears all conditions added by {on} # # query.on(:id, '>', '1') # query.clear_conditions! # def clear_conditions! @has_requested = false conditions.clear end # Adds a condition to the query; may be chained. # # query.on(:email, '=', 'value').on('created_at', '>', '2011-04-20') # # @param [String, Symbol] field name # @param [String] operator can use: `[ =, !=, <, >, >=, <=]` # @param [String] value to search for # @return self def on(field, operator, value) @has_requested = false @conditions << { :field => field, :type => operator, :value => value } self end # Send the built request to eloqua # # query.on(:email, '=', '*') # wildcard # query.request! # query.collection # Array of results # query.current_page # Current page # query.total_pages # Number of pages # # @return self def request! return if has_requested? sleep_for = wait_for_request_delay if(sleep_for) sleep(sleep_for) end xml_query = api.builder do |xml| xml.eloquaType do xml.template!(:object_type, remote_object.remote_type) end xml.searchQuery(build_query) # This won't harm anything but it is changing the # fields to its mapped elouqa name. if(!fields.blank? && fields.is_a?(Array)) fields.map! do |field| field = remote_object.eloqua_attribute(field) end xml.fieldNames do xml.template!(:array, fields) end end xml.pageNumber(page) xml.pageSize(limit) end # Add timestamp of when we made request @_query_started = Time.now result = api.request(:query, xml_query) # Clear collection collection.clear # Mark as requested @has_requested = true if(result[:entities]) @total_pages = result[:total_pages].to_i entities = Eloqua.format_results_for_array(result, :entities, :dynamic_entity) records = entities.inject([]) do |records, entity| record_attrs = {} entity_id = entity[:id] entity[:field_value_collection][:entity_fields].each do |entity_attr| record_attrs[entity_attr[:internal_name]] = entity_attr[:value] end record_attrs[remote_object.primary_key] = entity_id record_object = remote_object.new(record_attrs, :remote) collection << record_object end collection else @total_pages = 0 false end end def wait_for_request_delay if(request_delay && query_started) now = Time.now wait_until = Time.at((request_delay + query_started.to_f)) if(wait_until > now) wait_until - now end else false end end # Has the request been made yet? # # query.has_requested? # false # query.on(:email, '=', '*').request! # query.has_requested? # true # # @return [Boolean] def has_requested? @has_requested end # Sends request if not already set and iterates through result # # query.each do |record| # record.class # query.remote_object # end # # Currently this is a shortcut for # # query.all.each do |record| # ... # end # # @param [Proc] a block iterator def each(&block) all.each(&block) end # Sends request and returns collection # # query.on(:email, '=', '*').all # => [Eloqua::RemoteObject.new(), ...] # # @return [Array] collection of Eloqua::RemoteObject def all request! collection end # Iterates through each page up to max_pages # when max_pages is nil (default) will iterate through # each page yielding a block with a record. # # with max_pages you could and then resume the loop through # pages # # query.each_page(2) |record| # query.total_pages # 10 # query.page # 1 ... 2 # end # # ... # # query.each_page(2) |record| # query.total_pages # 10 # query.page # 3 ... 4 # end # # @see Query#each # @param [Integer] max pages to iterate through def each_page(max_pages = nil, &block) each(&block) while(total_pages > page) break if max_pages && page >= max_pages page(page + 1) each(&block) end end protected # Builds query from conditions # conditions are assembled by their field, type and then value as below # # #{field}#{type}'#{value}' # # then joined by AND which acts more like an SQL "OR" in Eloqua # > and < are escaped with > and < def build_query conditions.inject([]) do |parts, cond| part = "" part << remote_object.eloqua_attribute(cond[:field]) part << cond[:type].to_s part << "'#{cond[:value]}'" parts << part end.join(" AND ") end end end