class ExperianConsumerView::Client

Top-level wrapper for accessing the ExperianConsumerView API. Once an instance is created with the appropriate credentials, the lookup method provides the ability to lookup individuals, households, or postcodes in the ConsumerView API and return all the data your account has access to.

This class automatically handles logging in to the ConsumerView API, obtaining an authorisation token (which is valid for approximately 30 minutes), and then looking up the data. The authorisation token is cached so that it's not necessary to login again for every single lookup request.

Note that by default the authorisation is cached in-memory using ActiveSupport::Cache::MemoryStore. This is suitable for single-server applications, but is unlikely to be suitable for distributed applications, or those hosted on cloud infrastructure. A distributed cache, such as ActiveSupport::Cache::RedisCacheStore or ActiveSupport::Cache::MemCacheStore is recommended for distributed or cloud-hosted applications.

If an in-memory data-store were used in distributed or cloud-hosted applications, then the multiple servers will be unaware of each others tokens, and therefore each server would login to the ConsumerView API independently, even if another server already had a valid token. Logging in to the ConsumerView API multiple times with the same credentials will revoke prior tokens, meaning other servers will find their cached tokens are invalid the next time they try a lookup. This will likely lead to a situation where many lookup attempts fail the first time due to the server in question not having the most up-to-date token.

Constants

CACHE_KEY

Attributes

result_transformer[W]

Public Class Methods

new(user_id:, password:, client_id:, asset_id:, options: {}) click to toggle source

@param user_id [String] the username / email used to authorize use of the ConsumerView API @param password [String] the password used to authorize use of the ConsumerView API @param client_id [String] your 5-digit Experian client ID @param asset_id [String] your 6-character Experian asset ID @param options [Hash] a hash of advanced options for configuring the client

@option options [ActiveSupport::Cache] :token_cache optional cache to store login tokens. If no cache is provided,

a default in-memory cache is used, however such a cache is not suitable for distributed or cloud environments,
and will likely result in frequently invalidating the Experian ConsumerView authorization token.

@option options [#transform] :result_transformer optional object whose transform method accepts a hash

containing the results returned by the ConsumerView API for a single individual, household or postcode, and
transforms this hash into the desired output. By default, an instance of +ResultTransformer+ is used, which will
transform some common attributes returned by the ConsumerView API into hashes with richer details than returned
by the raw API.

@option options [String] :api_base_url optional base URL to make ConsumerView API calls against. By default, uses

the Experian production ConsumerView server.
# File lib/experian_consumer_view/client.rb, line 52
def initialize(user_id:, password:, client_id:, asset_id:, options: {})
  @user_id = user_id
  @password = password
  @client_id = client_id
  @asset_id = asset_id

  @token_cache = options[:token_cache] || default_token_cache
  @result_transformer = options[:result_transformer] || default_result_transformer
  @api = ExperianConsumerView::Api.new(url: options[:api_base_url])
end

Public Instance Methods

lookup(search_items:, auto_retries: 1) click to toggle source

Looks up 1 or more search items in the ConsumerView API.

Note that the demographic / propensity keys returned will only be those which the client & asset have access to. Refer to the Experian ConsumerView API Documentation for exact details of the keys & possible values.

@param search_items [Hash] a hash of identifiers to search keys for an individual / household / postcode as

required by the ConsumerView API. Eg.
<tt>{ "PersonA" => { "email" => "person.a@example.com" }, "Postcode1" => { "postcode" => "SW1A 1AA" } }</tt>.
Note that the top-level key is not passed to the ConsumerView API, it is just used for convenience when
returning results.

@param auto_retries [Integer] optional number of times the lookup should be retried if a transient / potentially

recoverable error occurs. Defaults to 1

@returns [Hash] a hash of identifiers to the results returned by the ConsumerView API. Eg.

<tt>
{
   "PersonA" => { "pc_mosaic_uk_7_group":"G", "Match":"P" } ,
   "Postcode1" => { "pc_mosaic_uk_7_group":"G", "Match":"PC" }
}
</tt>
# File lib/experian_consumer_view/client.rb, line 83
def lookup(search_items:, auto_retries: 1)
  ordered_identifiers = search_items.keys
  ordered_terms = search_items.values

  token = auth_token
  attempts = 0
  begin
    ordered_results = @api.batch_lookup(
      user_id: @user_id,
      token: token,
      client_id: @client_id,
      asset_id: @asset_id,
      batched_search_keys: ordered_terms
    )
  rescue ApiBadCredentialsError, ApiServerRefreshingError => e
    # Bad Credentials can sometimes be caused by race conditions - eg. one thread / server updating the cached
    # token while another is querying with the old token. Retrying once should avoid the client throwing
    # unnecessary errors to the calling code.
    # Experian docs also recommend retrying when a server refresh is in progress, and if that fails, retrying again
    # in approximately 10 minutes.
    raise e unless attempts < auto_retries

    token = auth_token(force_lookup: true)
    attempts += 1
    retry
  end

  results_hash(identifiers: ordered_identifiers, results: ordered_results)
end

Private Instance Methods

auth_token(force_lookup: false) click to toggle source
# File lib/experian_consumer_view/client.rb, line 115
def auth_token(force_lookup: false)
  # ConsumerView auth tokens last for 30 minutes before expiring & becoming invalid.
  # After 29 minutes, the cache entry will expire, and the first process to find the expired entry will refresh it,
  # while allowing other processes to use the existing value for another 10s. This should alleviate race conditions,
  # but will not eliminate them entirely. Note that in a distributed / cloud / multi-server environment, a shared
  # cache MUST be used. An in-memory store would mean multiple instances logging to the ConsumerView API, and each
  # login will change the active token, which other servers will not see, leading to frequent authorisation
  # failures.
  @token_cache.fetch(
    CACHE_KEY, expires_in: 29.minutes, race_condition_ttl: 10.seconds, force: force_lookup
  ) do
    @api.get_auth_token(user_id: @user_id, password: @password)
  end
end
default_result_transformer() click to toggle source
# File lib/experian_consumer_view/client.rb, line 148
def default_result_transformer
  ExperianConsumerView::Transformers::ResultTransformer.default
end
default_token_cache() click to toggle source
# File lib/experian_consumer_view/client.rb, line 144
def default_token_cache
  ActiveSupport::Cache::MemoryStore.new
end
results_hash(identifiers:, results:) click to toggle source
# File lib/experian_consumer_view/client.rb, line 130
def results_hash(identifiers:, results:)
  raise ApiResultSizeMismatchError unless results.size == identifiers.size

  # Construct a hash of { identifier => result_hash }
  # Hash[identifiers.zip(results)]

  results_hash = {}
  results.each_with_index do |single_result, i|
    results_hash[identifiers[i]] = @result_transformer.transform(single_result)
  end

  results_hash
end