class Gecko::Record::BaseAdapter

Attributes

client[R]
last_response[R]

Public Class Methods

new(client, model_name) click to toggle source

Instantiates a new Record Adapter

@param [Gecko::Client] client @param [String] model_name

@return [undefined]

@api private

# File lib/gecko/record/base_adapter.rb, line 15
def initialize(client, model_name)
  @client       = client
  @model_name   = model_name
  @identity_map = {}
end

Public Instance Methods

build(attributes = {}) click to toggle source

Build a new record

@example

new_order = client.Order.build(company_id: 123, order_number: 1234)

@example

new_order = client.Order.build
new_order.order_number = 1234

@param [#to_hash] initial attributes to set up the record

@return [Gecko::Record::Base]

@api public

# File lib/gecko/record/base_adapter.rb, line 262
def build(attributes = {})
  model_class.new(@client, attributes)
end
count(params = {}) click to toggle source

Returns the total count for a record type via API request.

@example

client.Product.count

@param [#to_hash] params

@return [Integer] Total number of available records

@api public

# File lib/gecko/record/base_adapter.rb, line 175
def count(params = {})
  where(params.merge(limit: 0))
  @pagination['total_records']
end
extract_collection(json) click to toggle source

Extract a collection from an API response

@return [Hash]

@api private

# File lib/gecko/record/base_adapter.rb, line 235
def extract_collection(json)
  json[plural_path]
end
extract_record(json) click to toggle source

Extract a record from an API response

@return Hash

@api private

# File lib/gecko/record/base_adapter.rb, line 244
def extract_record(json)
  json && json[json_root]
end
fetch(id) click to toggle source

Fetch a record via API, regardless of whether it is already in identity map.

@example

client.Product.fetch(12)

@param [Integer] id ID of record

@return [Gecko::Record::Base] if a record was found @return [nil] if no record was found

@api private

# File lib/gecko/record/base_adapter.rb, line 204
def fetch(id) # rubocop:disable Metrics/MethodLength
  verify_id_presence!(id)
  response = request(:get, plural_path + '/' + id.to_s)
  record_json = extract_record(response.parsed)
  instantiate_and_register_record(record_json)
rescue OAuth2::Error => e
  case e.response.status
  when 404
    record_not_found!(id)
  else
    raise
  end
end
find(id) click to toggle source

Find a record via ID, first searches the Identity Map, then makes an API request.

@example

client.Product.find(12)

@param [Integer] id ID of record

@return [Gecko::Record::Base] if a record was found

either in the identity map or via the API

@return [nil] If no record was found

@api public

# File lib/gecko/record/base_adapter.rb, line 34
def find(id)
  if has_record_for_id?(id)
    record_for_id(id)
  else
    fetch(id)
  end
end
find_many(ids) click to toggle source

Find multiple records via IDs, searching the Identity Map, then making an API request for remaining records. May return nulls

@example

client.Product.find_many([12, 13, 14])

@param [Array<Integer>] ids IDs of records to fetch

@return [Array<Gecko::Record::Base>] Records for the ids

either in the identity map or via the API

@api public

# File lib/gecko/record/base_adapter.rb, line 80
def find_many(ids)
  existing, required = ids.partition { |id| has_record_for_id?(id) }
  if required.any?
    where(ids: required) + existing.map { |id| record_for_id(id) }
  else
    existing.map { |id| record_for_id(id) }
  end
end
first(params = {}) click to toggle source

Fetch the first record for the given parameters

@example Fetch via ID

client.Product.first

@example Fetch via date

client.Product.first(updated_at_min: "2014-03-03T21:09:00")

@example Search

client.Product.first(q: "gecko")

@param [#to_hash] params @option params [String] :q Search query @option params [Array<Integer>] :ids IDs to search for @option params [String] :updated_at_min Last updated_at minimum @option params [String] :updated_at_max Last updated_at maximum @option params [String] :order Sort order i.e 'name asc' @option params [String, Array<String>] :status Record status/es

@return <Gecko::Record::Base> A record instance

@api public

# File lib/gecko/record/base_adapter.rb, line 154
def first(params = {})
  where(params.merge(limit: 1)).first
end
forty_two(params = {}) click to toggle source

Fetch the forty-second record for the given parameters

@api public

# File lib/gecko/record/base_adapter.rb, line 161
def forty_two(params = {})
  where(params.merge(limit: 1, page: 42)).first
end
has_record_for_id?(id) click to toggle source

Returns whether the Identity Map has a record for a particular ID

@example

client.Product.has_record_for_id?(12)

@return [Boolean] if a record was found in the identity map.

@api private

# File lib/gecko/record/base_adapter.rb, line 64
def has_record_for_id?(id)
  @identity_map.key?(id)
end
instantiate_and_register_record(record_json) click to toggle source

Instantiates a record from it's JSON representation and registers it into the identity map

@return [Gecko::Record::Base]

@api private

# File lib/gecko/record/base_adapter.rb, line 290
def instantiate_and_register_record(record_json)
  record = model_class.new(@client, record_json)
  register_record(record)
  record
end
parse_records(json) click to toggle source

Parse a json collection and instantiate records

@return [Array<Gecko::Record::Base>]

@api private

# File lib/gecko/record/base_adapter.rb, line 223
def parse_records(json)
  parse_sideloaded_records(json)
  extract_collection(json).map do |record_json|
    instantiate_and_register_record(record_json)
  end
end
peek_all() click to toggle source

Returns all the records currently in the identity map.

@example Return all Products previously fetched

client.Product.peek_all

@return [Array<Gecko::Record::Base>]

@api public

# File lib/gecko/record/base_adapter.rb, line 128
def peek_all
  @identity_map.values
end
record_for_id(id) click to toggle source

Searches the Identity Map for a record via ID

@example

client.Product.record_for_id(12)

@return [Gecko::Record::Base] if a record was found in the identity map. @raise [Gecko::Record::RecordNotInIdentityMap] If no record was found

@api private

# File lib/gecko/record/base_adapter.rb, line 51
def record_for_id(id)
  verify_id_presence!(id)
  @identity_map.fetch(id) { record_not_in_identity_map!(id) }
end
save(record, opts = {}) click to toggle source

Save a record

@params [Object] :record A Gecko::Record object @param [Hash] opts the options to make the request with @option opts [Hash] :idempotency_key A unique identifier for this action

@return [Boolean] whether the save was successful.

If false the record will contain an errors hash

@api private

# File lib/gecko/record/base_adapter.rb, line 276
def save(record, opts = {})
  if record.persisted?
    update_record(record, opts)
  else
    create_record(record, opts)
  end
end
size() click to toggle source

Returns the total count for a record type. Reads from the last request or makes a new request if not available.

@example

client.Product.size

@return [Integer] Total number of available records

@api public

# File lib/gecko/record/base_adapter.rb, line 189
def size
  (defined?(@pagination) && @pagination['total_records']) || count
end
where(params = {}) click to toggle source

Fetch a record collection via the API. Parameters vary via Record Type

@example Fetch via ID

client.Product.where(ids: [1,2])

@example Fetch via date

client.Product.where(updated_at_min: "2014-03-03T21:09:00")

@example Search

client.Product.where(q: "gecko")

@param [#to_hash] params @option params [String] :q Search query @option params [Integer] :page (1) Page number for pagination @option params [Integer] :limit (100) Page limit for pagination @option params [Array<Integer>] :ids IDs to search for @option params [String] :updated_at_min Last updated_at minimum @option params [String] :updated_at_max Last updated_at maximum @option params [String] :order Sort order i.e 'name asc' @option params [String, Array<String>] :status Record status/es

@return [Array<Gecko::Record::Base>] Records via the API

@api public

# File lib/gecko/record/base_adapter.rb, line 113
def where(params = {})
  response = request(:get, plural_path, params: params)
  parsed_response = response.parsed
  set_pagination(response.headers)
  parse_records(parsed_response)
end

Private Instance Methods

create_record(record, opts = {}) click to toggle source

Create a record via API

@return [OAuth2::Response]

@api private

# File lib/gecko/record/base_adapter.rb, line 345
def create_record(record, opts = {})
  response = request(:post, plural_path, {
    body:         record.as_json,
    raise_errors: false
  }.merge(headers: headers_from_opts(opts)))
  handle_response(record, response)
end
handle_response(record, response) click to toggle source

Handle the API response.

  • Updates the record if attributes are returned

  • Adds validation errors from a 422

@return [OAuth2::Response]

@api private

# File lib/gecko/record/base_adapter.rb, line 373
def handle_response(record, response) # rubocop:disable Metrics/MethodLength
  case response.status
  when 200..299
    if (response_json = extract_record(response.parsed))
      record.attributes = response_json
      register_record(record)
    end
    true
  when 422
    record.errors.from_response(response.parsed['errors'])
    false
  else
    fail OAuth2::Error, response
  end
end
headers_from_opts(opts) click to toggle source

Applies an idempotency key to the request if provided

@api private

# File lib/gecko/record/base_adapter.rb, line 399
def headers_from_opts(opts)
  headers = {}
  headers['Idempotency-Key'] = opts[:idempotency_key] if opts[:idempotency_key]
  headers
end
json_root() click to toggle source

Returns the json key for a record adapter

@example

product_adapter.json_root #=> "product"

@return [String]

@api private

# File lib/gecko/record/base_adapter.rb, line 306
def json_root
  @model_name.to_s.underscore
end
model_class() click to toggle source

Returns the model class associated with an adapter

@example

product_adapter.model_class #=> Gecko::Record::Product

@return [Class]

@api private

# File lib/gecko/record/base_adapter.rb, line 327
def model_class
  Gecko::Record.const_get(@model_name)
end
parse_sideloaded_records(json) click to toggle source

Parse and instantiate sideloaded records

@api private

# File lib/gecko/record/base_adapter.rb, line 408
def parse_sideloaded_records(json)
  json.each do |record_type, records|
    next if record_type == "meta"
    next if record_type == @model_name.to_s

    record_class = record_type.singularize.classify
    next unless Gecko::Record.const_defined?(record_class)

    adapter = @client.adapter_for(record_class)

    records.each do |record_json|
      adapter.instantiate_and_register_record(record_json)
    end
  end
end
plural_path() click to toggle source

Returns the pluralized name of a record class used for generating API endpoint

@return [String]

@api private

# File lib/gecko/record/base_adapter.rb, line 315
def plural_path
  json_root + 's'
end
record_not_found!(id) click to toggle source
# File lib/gecko/record/base_adapter.rb, line 455
def record_not_found!(id)
  fail RecordNotFound, "Couldn't find #{model_class.name} with id=#{id}"
end
record_not_in_identity_map!(id) click to toggle source
# File lib/gecko/record/base_adapter.rb, line 459
def record_not_in_identity_map!(id)
  fail RecordNotInIdentityMap, "Couldn't find #{model_class.name} with id=#{id}"
end
register_record(record) click to toggle source

Registers a record into the identity map

@return [Gecko::Record::Base]

@api private

# File lib/gecko/record/base_adapter.rb, line 336
def register_record(record)
  @identity_map[record.id] = record
end
request(verb, path, options = {}) click to toggle source

Makes a request to the API.

@param [Symbol] verb the HTTP request method @param [String] path the HTTP URL path of the request @param [Hash] opts the options to make the request with @option opts [Hash] :params params for request

@return [OAuth2::Response]

@api private

# File lib/gecko/record/base_adapter.rb, line 434
def request(verb, path, options = {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
  ActiveSupport::Notifications.instrument('request.gecko') do |payload|
    payload[:verb]         = verb
    payload[:params]       = options[:params]
    payload[:body]         = options[:body]
    payload[:model_class]  = model_class
    payload[:request_path] = path

    options[:headers]      = options.fetch(:headers, {}).tap do |headers|
      headers['Content-Type'] = 'application/json'
    end

    options[:body] = options[:body].to_json if options[:body]

    @client.access_token.request(verb, path, options.merge(raise_errors: false)).tap do |response|
      payload[:response] = @last_response = response
      raise response.error if response.error && options[:raise_errors] != false
    end
  end
end
set_pagination(headers) click to toggle source

Sets up the pagination metadata on a record adapter

@api private

# File lib/gecko/record/base_adapter.rb, line 392
def set_pagination(headers)
  @pagination = JSON.parse(headers["x-pagination"]) if headers["x-pagination"]
end
update_record(record, opts = {}) click to toggle source

Update a record via API

@return [OAuth2::Response]

@api private

# File lib/gecko/record/base_adapter.rb, line 358
def update_record(record, opts = {})
  response = request(:put, plural_path + "/" + record.id.to_s, {
    body:         record.as_json,
    raise_errors: false
  }.merge(headers: headers_from_opts(opts)))
  handle_response(record, response)
end
verify_id_presence!(id) click to toggle source
# File lib/gecko/record/base_adapter.rb, line 463
def verify_id_presence!(id)
  return unless id.respond_to?(:empty?) ? id.empty? : !id

  fail RecordNotFound, "Couldn't find #{model_class.name} without an ID"
end