class ShopifyAPI::Rest::Base

Attributes

atomic_hash_attributes[R]
custom_prefix[R]
has_many[R]
has_one[R]
headers[RW]
errors[R]
original_state[RW]

Public Class Methods

api_call_limit() click to toggle source
# File lib/shopify_api/rest/base.rb, line 151
def api_call_limit
  instance_variable_get(:"@api_call_limit").value
end
base_find(session: nil, ids: {}, params: {}) click to toggle source
# File lib/shopify_api/rest/base.rb, line 88
def base_find(session: nil, ids: {}, params: {})
  session ||= ShopifyAPI::Context.active_session

  client = ShopifyAPI::Clients::Rest::Admin.new(session: session)

  path = T.must(get_path(http_method: :get, operation: :get, ids: ids))
  response = client.get(path: path, query: params.compact, headers: headers)

  instance_variable_get(:"@prev_page_info").value = response.prev_page_info
  instance_variable_get(:"@next_page_info").value = response.next_page_info

  instance_variable_get(:"@retry_request_after").value = response.retry_request_after
  instance_variable_get(:"@api_call_limit").value = response.api_call_limit

  create_instances_from_response(response: response, session: T.must(session))
end
class_name() click to toggle source
# File lib/shopify_api/rest/base.rb, line 106
def class_name
  T.must(name).demodulize.underscore
end
create_instance(data:, session:, instance: nil) click to toggle source
# File lib/shopify_api/rest/base.rb, line 276
def create_instance(data:, session:, instance: nil)
  instance ||= new(session: session)
  instance.original_state = {}

  data.each do |attribute, value|
    attr_sym = attribute.to_sym

    if has_many?(attr_sym) && value
      instance.original_state[attr_sym] = []
      attr_list = []
      value.each do |element|
        child = T.unsafe(@has_many[attr_sym]).create_instance(data: element, session: session)
        attr_list << child
        instance.original_state[attr_sym] << child.to_hash(true)
      end
      instance.public_send("#{attribute}=", attr_list)
    elsif has_one?(attr_sym) && value
      # force a hash if core returns values that instantiate objects like "USD"
      data_hash = value.is_a?(Hash) ? value : { attribute.to_s => value }
      child = T.unsafe(@has_one[attr_sym]).create_instance(data: data_hash, session: session)
      instance.public_send("#{attribute}=", child)
      instance.original_state[attr_sym] = child.to_hash(true)
    else
      instance.public_send("#{attribute}=", value)
      instance.original_state[attr_sym] = value
    end
  end

  instance
end
create_instances_from_response(response:, session:) click to toggle source
# File lib/shopify_api/rest/base.rb, line 253
def create_instances_from_response(response:, session:)
  objects = []

  body = T.cast(response.body, T::Hash[String, T.untyped]).with_indifferent_access

  response_names = json_response_body_names

  response_names.each do |response_name|
    if body.key?(response_name.pluralize) || (body.key?(response_name) && body[response_name].is_a?(Array))
      (body[response_name.pluralize] || body[response_name]).each do |entry|
        objects << create_instance(data: entry, session: session)
      end
    elsif body.key?(response_name)
      objects << create_instance(data: body[response_name], session: session)
    end
  end

  objects
end
get_path(http_method:, operation:, entity: nil, ids: {}) click to toggle source
# File lib/shopify_api/rest/base.rb, line 178
def get_path(http_method:, operation:, entity: nil, ids: {})
  match = T.let(nil, T.nilable(String))
  max_ids = T.let(-1, Integer)
  @paths.each do |path|
    next if http_method != path[:http_method] || operation != path[:operation]

    path_ids = T.cast(path[:ids], T::Array[Symbol])

    url_ids = ids.transform_keys(&:to_sym)
    path_ids.each do |id|
      if url_ids[id].nil? && (entity_id = entity&.public_send(id))
        url_ids[id] = entity_id
      end
    end

    url_ids.compact!

    # We haven't found all the required ids or we have a more specific match
    next if !(path_ids - url_ids.keys).empty? || path_ids.length <= max_ids

    max_ids = path_ids.length
    match = T.cast(path[:path], String).gsub(/(<([^>]+)>)/) do
      url_ids[T.unsafe(Regexp.last_match)[2].to_sym]
    end
  end

  custom_prefix ? "#{T.must(custom_prefix).sub(%r{\A/}, "")}/#{match}" : match
end
get_path_ids(http_method:, operation:) click to toggle source
# File lib/shopify_api/rest/base.rb, line 213
def get_path_ids(http_method:, operation:)
  found_path = @paths.find do |path|
    http_method == path[:http_method] && operation == path[:operation]
  end
  return nil if found_path.nil?

  T.cast(found_path[:ids], T::Array[Symbol])
end
has_many?(attribute) click to toggle source
# File lib/shopify_api/rest/base.rb, line 156
def has_many?(attribute)
  @has_many.include?(attribute)
end
has_one?(attribute) click to toggle source
# File lib/shopify_api/rest/base.rb, line 161
def has_one?(attribute)
  @has_one.include?(attribute)
end
inherited(subclass) click to toggle source
Calls superclass method
# File lib/shopify_api/rest/base.rb, line 73
def inherited(subclass)
  super

  subclass.define_singleton_method(:headers) do
    ShopifyAPI::Rest::Base.headers
  end
end
json_body_name() click to toggle source
# File lib/shopify_api/rest/base.rb, line 116
def json_body_name
  class_name.underscore
end
json_response_body_names() click to toggle source
# File lib/shopify_api/rest/base.rb, line 121
def json_response_body_names
  [class_name]
end
new(session: nil, from_hash: nil) click to toggle source
# File lib/shopify_api/rest/base.rb, line 35
def initialize(session: nil, from_hash: nil)
  @original_state = T.let({}, T::Hash[Symbol, T.untyped])
  @custom_prefix = T.let(nil, T.nilable(String))
  @forced_nils = T.let({}, T::Hash[String, T::Boolean])
  @aliased_properties = T.let({}, T::Hash[String, String])

  session ||= ShopifyAPI::Context.active_session

  client = ShopifyAPI::Clients::Rest::Admin.new(session: session)

  @session = T.let(T.must(session), Auth::Session)
  @client = T.let(client, Clients::Rest::Admin)
  @errors = T.let(Rest::BaseErrors.new, Rest::BaseErrors)

  from_hash&.each do |key, value|
    set_property(key, value)
  end
end
next_page?() click to toggle source
# File lib/shopify_api/rest/base.rb, line 141
def next_page?
  !instance_variable_get(:"@next_page_info").value.nil?
end
next_page_info() click to toggle source
# File lib/shopify_api/rest/base.rb, line 131
def next_page_info
  instance_variable_get(:"@next_page_info").value
end
prev_page?() click to toggle source
# File lib/shopify_api/rest/base.rb, line 136
def prev_page?
  !instance_variable_get(:"@prev_page_info").value.nil?
end
prev_page_info() click to toggle source
# File lib/shopify_api/rest/base.rb, line 126
def prev_page_info
  instance_variable_get(:"@prev_page_info").value
end
primary_key() click to toggle source
# File lib/shopify_api/rest/base.rb, line 111
def primary_key
  "id"
end
read_only_attributes() click to toggle source
# File lib/shopify_api/rest/base.rb, line 166
def read_only_attributes
  @read_only_attributes&.map { |a| :"@#{a}" }
end
request(http_method:, operation:, session:, ids: {}, params: {}, body: nil, entity: nil) click to toggle source
# File lib/shopify_api/rest/base.rb, line 233
def request(http_method:, operation:, session:, ids: {}, params: {}, body: nil, entity: nil)
  client = ShopifyAPI::Clients::Rest::Admin.new(session: session)

  path = get_path(http_method: http_method, operation: operation.to_sym, ids: ids)

  case http_method
  when :get
    client.get(path: T.must(path), query: params.compact, headers: headers)
  when :post
    client.post(path: T.must(path), query: params.compact, body: body || {}, headers: headers)
  when :put
    client.put(path: T.must(path), query: params.compact, body: body || {}, headers: headers)
  when :delete
    client.delete(path: T.must(path), query: params.compact, headers: headers)
  else
    raise Errors::InvalidHttpRequestError, "Invalid HTTP method: #{http_method}"
  end
end
retry_request_after() click to toggle source
# File lib/shopify_api/rest/base.rb, line 146
def retry_request_after
  instance_variable_get(:"@retry_request_after").value
end

Public Instance Methods

delete(params: {}) click to toggle source
# File lib/shopify_api/rest/base.rb, line 372
def delete(params: {})
  @client.delete(
    path: T.must(self.class.get_path(http_method: :delete, operation: :delete, entity: self)),
    query: params.compact,
    headers: self.class.headers,
  )
rescue ShopifyAPI::Errors::HttpResponseError => e
  @errors.errors << e
  raise
end
method_missing(meth_id, val = nil) click to toggle source
# File lib/shopify_api/rest/base.rb, line 309
def method_missing(meth_id, val = nil)
  match = meth_id.id2name.match(/([^=]+)(=)?/)

  var = T.must(T.must(match)[1])

  if T.must(match)[2]
    set_property(var, val)
    @forced_nils[var] = val.nil?
  else
    get_property(var)
  end
end
respond_to_missing?(meth_id, *args) click to toggle source
Calls superclass method
# File lib/shopify_api/rest/base.rb, line 323
def respond_to_missing?(meth_id, *args)
  str = meth_id.id2name
  match = str.match(/([^=]+)=/)

  match.nil? ? true : super
end
save(update_object: false) click to toggle source
# File lib/shopify_api/rest/base.rb, line 389
def save(update_object: false)
  method = deduce_write_verb

  body = {
    self.class.json_body_name => attributes_to_update.merge(build_required_attributes(http_method: method)),
  }

  response = @client.public_send(
    method,
    body: body,
    path: deduce_write_path(method),
    headers: self.class.headers,
  )

  if update_object
    response_name = self.class.json_response_body_names & response.body.keys
    self.class.create_instance(data: response.body[response_name.first], session: @session, instance: self)
  end
rescue ShopifyAPI::Errors::HttpResponseError => e
  @errors.errors << e
  raise
end
save!() click to toggle source
# File lib/shopify_api/rest/base.rb, line 384
def save!
  save(update_object: true)
end
to_hash(saving = false) click to toggle source
# File lib/shopify_api/rest/base.rb, line 331
def to_hash(saving = false)
  hash = {}
  instance_variables.each do |var|
    next if [
      :"@original_state",
      :"@session",
      :"@client",
      :"@forced_nils",
      :"@errors",
      :"@aliased_properties",
    ].include?(var)
    next if saving && self.class.read_only_attributes&.include?(var)

    var = var.to_s.delete("@")
    attribute = if @aliased_properties.value?(var)
      T.must(@aliased_properties.key(var))
    else
      var
    end.to_sym

    if self.class.has_many?(attribute)
      attribute_class = self.class.has_many[attribute]
      hash[attribute.to_s] = get_property(attribute).map do |element|
        get_element_hash(element, T.unsafe(attribute_class), saving)
      end.to_a if get_property(attribute)
    elsif self.class.has_one?(attribute)
      element_hash = get_element_hash(
        get_property(attribute),
        T.unsafe(self.class.has_one[attribute]),
        saving,
      )
      hash[attribute.to_s] = element_hash if element_hash || @forced_nils[attribute.to_s]
    elsif !get_property(attribute).nil? || @forced_nils[attribute.to_s]
      hash[attribute.to_s] =
        get_property(attribute)
    end
  end
  hash
end

Private Instance Methods

attributes_to_update() click to toggle source
# File lib/shopify_api/rest/base.rb, line 415
def attributes_to_update
  updatable_attributes = original_state.reject do |attribute, _|
    self.class.read_only_attributes&.include?("@#{attribute}".to_sym)
  end

  stringified_updatable_attributes = deep_stringify_keys(updatable_attributes)
  stringified_new_attributes = deep_stringify_keys(to_hash(true))
  ShopifyAPI::Utils::AttributesComparator.compare(
    stringified_updatable_attributes,
    stringified_new_attributes,
    atomic_hash_attributes: self.class.atomic_hash_attributes || [],
  )
end
build_required_attributes(http_method:) click to toggle source
# File lib/shopify_api/rest/base.rb, line 430
def build_required_attributes(http_method:)
  required_attributes = {}

  primary_key_value = send(self.class.primary_key)
  unless primary_key_value.nil?
    required_attributes[self.class.primary_key] = primary_key_value
  end

  path_ids = deduce_path_ids(http_method)
  path_ids&.each do |path_id|
    path_id_value = send(path_id)
    required_attributes[path_id.to_s] = path_id_value unless path_id_value.nil?
  end

  required_attributes
end
deduce_path_ids(method) click to toggle source
# File lib/shopify_api/rest/base.rb, line 465
def deduce_path_ids(method)
  path_ids = self.class.get_path_ids(http_method: method, operation: method)

  if path_ids.nil?
    method = method == :post ? :put : :post
    path_ids = self.class.get_path_ids(http_method: method, operation: method)
  end

  path_ids
end
deduce_write_path(method) click to toggle source
# File lib/shopify_api/rest/base.rb, line 453
def deduce_write_path(method)
  path = self.class.get_path(http_method: method, operation: method, entity: self)

  if path.nil?
    method = method == :post ? :put : :post
    path = self.class.get_path(http_method: method, operation: method, entity: self)
  end

  path
end
deduce_write_verb() click to toggle source
# File lib/shopify_api/rest/base.rb, line 448
def deduce_write_verb
  send(self.class.primary_key) ? :put : :post
end
deep_stringify_keys(hash) click to toggle source
# File lib/shopify_api/rest/base.rb, line 477
def deep_stringify_keys(hash)
  hash.each_with_object({}) do |(key, value), result|
    new_key = key.to_s
    new_value = value.is_a?(Hash) ? deep_stringify_keys(value) : value
    result[new_key] = new_value
  end
end
get_element_hash(element, attribute_class, saving) click to toggle source
# File lib/shopify_api/rest/base.rb, line 511
def get_element_hash(element, attribute_class, saving)
  return nil if element.nil?
  return element.to_hash(saving) unless element.is_a?(Hash)

  T.unsafe(attribute_class).create_instance(session: @session, data: element).to_hash(saving)
end
get_property(key) click to toggle source
# File lib/shopify_api/rest/base.rb, line 498
def get_property(key)
  clean_key = @aliased_properties.key?(key.to_s) ? @aliased_properties[key.to_s] : key

  instance_variable_get("@#{clean_key}")
end
set_property(key, val) click to toggle source
# File lib/shopify_api/rest/base.rb, line 486
def set_property(key, val)
  # Some API fields contain invalid characters, like `?`, which causes issues when setting them as instance
  # variables. To work around that, we're cleaning them up here but keeping track of the properties that were
  # aliased this way. When loading up the property, we can map back from the "invalid" field so that it is
  # transparent to outside callers
  clean_key = key.to_s.gsub(/[\?\s]/, "")
  @aliased_properties[key.to_s] = clean_key if clean_key != key

  instance_variable_set("@#{clean_key}", val)
end