# frozen_string_literal: true # Methods for working with instances of global to soql objects, not global overall # See https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_describe.htm module SoqlGlobalObjectData # @return [Hash] List of ids to delete when deleting a particular object attr_accessor :ids_to_delete # Override to handle removing dependent records def remove_dependent_records(_id); end # Return key and value to look up for a provided hash # @return [Array] Array with [column_to_lookup, value_to_look_for] def extract_lookup(lookup) raise 'Need to pass to Soql object key value pair to look up by' unless lookup.first.is_a? Array lookup_key, lookup_value = lookup.first raise 'Need to set lookup_key' unless lookup_key [lookup_key, lookup_value] end # Url enconding needs to be used when searching for special characters (+ => '%2B') # (see https://www.w3schools.com/tags/ref_urlencode.asp) # @param [String] soql_query String representing SOQL query # @param [Boolean] wait Whether to wait for record if no result returned # @example # query SELECT Name from Account WHERE Name = 'TEST Org 001' # This is converted to: # perform_query "SELECT+Name+from+Account+WHERE+Name+=+'TEST+Org+001'" # @return [Exchange] Exchange object from which JSON response can be obtained (i.e, with exchange.response) def query(soql_query, wait: true) rest_query = soql_query.gsub('%', '%25').gsub('+', '%2B').tr(' ', '+') if wait new("SOQL Query: #{soql_query}", method: :get, suburl: "query/?q=#{rest_query}").until(timeout: 20, interval: 1) do response.body.include? '"url"' # Could be waiting for element to be created end else new("SOQL Query: #{soql_query}", method: :get, suburl: "query/?q=#{rest_query}") end end # @param [Symbol, String] lookup Hash representing look up performed # @param [String] url Url to get def data_from_url(url, lookup) new("Id at #{url}", method: :get, suburl: url.split("v#{SoqlHandler.api_version}/").last) rescue NoElementAtPath raise NoElementAtPath, "No result found for #{lookup} under user #{LeapSalesforce.api_user}" end # For dates (ending with .000Z), query is always greater than # @param [Hash] lookup Hash to look up values according to # @return [String] SOQL query to filter results def soql_lookup_filter(lookup) limit = lookup.delete(:limit) conditional = '' lookup.each do |key, value| conditional_term = conditional.empty? ? 'WHERE' : 'AND' conditional += "#{conditional_term} #{key} #{condition_for(value)} " end query = conditional + 'ORDER BY CreatedDate DESC NULLS FIRST' query += " LIMIT #{limit}" if limit query end # Get the response data for a SoqlObject using the calling class's table. # Will get the latest created date. # @example # # Get a contact where LastName is 'Bob' # Contact.get(LastName: 'Bob') # @param [Hash] lookup Key value pair unique to Salesforce to query for # @option lookup [Boolean] :teardown Whether to remove id after scenario finished # @return [SoqlData] def get(lookup) teardown = lookup.delete(:teardown) SoqlHandler.new("Query on #{self}").use instance_to_get = if lookup.key? :Id new("Lookup id: #{lookup[:Id]}", method: :get, suburl: "sobjects/#{soql_object_name}/#{lookup[:Id]}") else initial_query = query "SELECT Id FROM #{soql_object_name} #{soql_lookup_filter(lookup)}" data_from_url initial_query['$..url'], lookup end SoqlData.ids_to_delete[self] = instance_to_get[:id] if teardown instance_to_get end # @return [Exchange] Result of looking up id based on lookup criteria def lookup_id(lookup) teardown = lookup.delete(:teardown) SoqlHandler.new("Query on #{self}").use result = query "SELECT Id FROM #{soql_object_name} #{soql_lookup_filter(lookup)}", wait: false SoqlData.ids_to_delete[self] = id if teardown result end # @return [String] Id that matches filter def id_where(lookup) lookup_id(lookup).id end # @return [Boolean] Whether any result for lookup def any_where?(lookup) lookup_id(lookup)[:totalSize] != 0 end # Perform the code in the block for all the ids matching a query. # If no block, return a list of objects # @param [Hash] lookup Key value pair unique to Salesforce to query for # @yield [id] Perform block for each id returned. The 'id' parameter in a block represents an id matching the query # @return [Array] List of ids matching criteria. Only used if no block given def each_id_with(lookup) lookup[:limit] ||= nil # Don't limit results returned SoqlHandler.new("Each Id where #{self}").use results = query "SELECT Id FROM #{soql_object_name} #{soql_lookup_filter(lookup)}", wait: false ids = results.ids if block_given? ids.each { |id| yield(id) } else ids end end # @param [Hash] lookup Key value pair unique to Salesforce to query for # @return [Array] List of Soql objects matching criteria def each_with(lookup) ids = each_id_with lookup ids.collect { |id| get(Id: id) } end # Remove all ids from table that match lookup criteria # @param [Hash] lookup Key value pair unique to Salesforce to query for def delete_ids_with(lookup) each_id_with(lookup, &method(:delete)) end # Remove object from Salesforce with provided id # @param [String] id Id of element to update # @param [Hash] data Key value pairs with data to update # @return [Exchange] Exchange object for object def update(id, data) must_pass = data.delete(:must_pass) data = data.transform_values do |value| value.is_a?(Time) ? value.salesforce_format : value end SoqlHandler.new("Update #{id}").use update = new("Update #{self}, #{id} with '#{data}'", method: :patch, suburl: "sobjects/#{soql_object_name}/#{id}", body: data) update.call return update unless must_pass successful? update end # Remove object from Salesforce with provided id # @param [String] id Id of element to remove # @param [Boolean] must_pass Whether to raise exception if call is not successful # @return [Exchange] Exchange object making delete call def delete(id, must_pass: false) SoqlData.ids_to_delete.reject! { |table, id_to_remove| table == self && id_to_remove == id } # Remove id from list to delete remove_dependent_records(id) SoqlHandler.new("Delete #{id}").use delete = new('SOQL Delete', method: :delete, suburl: "sobjects/#{soql_object_name}/#{id}") delete.call return delete unless must_pass successful? delete end # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/AbcSize def soql_element(name, backend_name) # Either set the element (if creating a new record) or update the object # @param [String] new_value Value to update record to define_method("#{name}=") do |new_value| if @response @response = update(backend_name => new_value).response else self[backend_name] = new_value.class < SoqlData ? new_value.id : new_value end end # @return [String] Value of backend name define_method name.to_s do begin self[backend_name] rescue NoElementAtPath raise diagnose_error if error_message? @response = get.response self[backend_name] end end # @return [String] Name of backend name for element define_method("#{name}_element") { backend_name } end # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/AbcSize private # @todo: Cover >= condition # @param [String] value Value to search for. Certain # @return [String] Condition criteria to match value def condition_for(value) operator = case value[0] when '>' then '>' when '<' then '<' when '~' then 'LIKE' else return "= '#{value}'" unless value.type_of_time? return "= #{value.to_zulu_date_string}" end value = value[1..-1] return "#{operator} #{value.to_zulu_date_string}" if value.type_of_time? "#{operator} '#{value}'" end end