Class: Attio::APIResource

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/attio/api_resource.rb

Overview

Base class for all API resources Provides standard CRUD operations in a clean, Ruby-like way

Defined Under Namespace

Classes: ListObject

Constant Summary collapse

SKIP_KEYS =

Keys to skip when processing attributes from API responses

%i[id created_at _metadata].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}, opts = {}) ⇒ APIResource

Returns a new instance of APIResource.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/attio/api_resource.rb', line 14

def initialize(attributes = {}, opts = {})
  @attributes = {}
  @original_attributes = {}
  @changed_attributes = Set.new
  @opts = opts
  @metadata = {}

  # Normalize attributes to use symbol keys
  normalized_attrs = normalize_attributes(attributes)

  # Extract metadata and system fields
  if normalized_attrs.is_a?(Hash)
    # Handle Attio's nested ID structure
    @id = extract_id(normalized_attrs[:id])
    @created_at = parse_timestamp(normalized_attrs[:created_at])
    @metadata = normalized_attrs[:_metadata] || {}

    # Process all attributes
    normalized_attrs.each do |key, value|
      next if SKIP_KEYS.include?(key)

      @attributes[key] = process_attribute_value(value)
      @original_attributes[key] = deep_copy(process_attribute_value(value))
    end
  end
end

Instance Attribute Details

#created_atObject (readonly)

Returns the value of attribute created_at.



9
10
11
# File 'lib/attio/api_resource.rb', line 9

def created_at
  @created_at
end

#idObject (readonly)

Returns the value of attribute id.



9
10
11
# File 'lib/attio/api_resource.rb', line 9

def id
  @id
end

#metadataObject (readonly)

Returns the value of attribute metadata.



9
10
11
# File 'lib/attio/api_resource.rb', line 9

def 
  @metadata
end

Class Method Details

.api_operations(*operations) ⇒ Object

Define which operations this resource supports Example: api_operations :list, :create, :retrieve, :update, :delete



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/attio/api_resource.rb', line 239

def api_operations(*operations)
  @supported_operations = operations

  operations.each do |operation|
    case operation
    when :list
      define_list_operation
    when :create
      define_create_operation
    when :retrieve
      define_retrieve_operation
    when :update
      define_update_operation
    when :delete
      define_delete_operation
    else
      raise ArgumentError, "Unknown operation: #{operation}"
    end
  end
end

.attr_attio(*attributes) ⇒ Object

Define attribute accessors for known attributes



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/attio/api_resource.rb', line 261

def attr_attio(*attributes)
  attributes.each do |attr|
    # Reader method
    define_method(attr) do
      self[attr]
    end

    # Writer method
    define_method("#{attr}=") do |value|
      self[attr] = value
    end

    # Predicate method
    define_method("#{attr}?") do
      !!self[attr]
    end
  end
end

.execute_request(method, path, params = {}, opts = {}) ⇒ Object

Execute HTTP request



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/attio/api_resource.rb', line 281

def execute_request(method, path, params = {}, opts = {})
  client = Attio.client(api_key: opts[:api_key])

  case method
  when :GET
    client.get(path, params)
  when :POST
    client.post(path, params)
  when :PUT
    client.put(path, params)
  when :PATCH
    client.patch(path, params)
  when :DELETE
    client.delete(path)
  else
    raise ArgumentError, "Unsupported method: #{method}"
  end
end

.id_param_name(id = nil) ⇒ Object

Get the ID parameter name (usually "id", but sometimes needs prefix)



301
302
303
# File 'lib/attio/api_resource.rb', line 301

def id_param_name(id = nil)
  :id
end

.prepare_params_for_create(params) ⇒ Object

Hook for subclasses to prepare params before create



311
312
313
# File 'lib/attio/api_resource.rb', line 311

def prepare_params_for_create(params)
  params
end

.prepare_params_for_update(params) ⇒ Object

Hook for subclasses to prepare params before update



316
317
318
# File 'lib/attio/api_resource.rb', line 316

def prepare_params_for_update(params)
  params
end

.resource_nameString

Get the resource name derived from the class name

Returns:

  • (String)

    The lowercase resource name



209
210
211
# File 'lib/attio/api_resource.rb', line 209

def self.resource_name
  name.split("::").last.downcase
end

.resource_pathString

Resource path helpers Get the base API path for this resource type

Returns:

  • (String)

    The API path (e.g., "/v2/objects")

Raises:

  • (NotImplementedError)

    Must be implemented by subclasses



203
204
205
# File 'lib/attio/api_resource.rb', line 203

def self.resource_path
  raise NotImplementedError, "Subclasses must implement resource_path"
end

.validate_id!(id) ⇒ Object

Validate an ID parameter

Raises:

  • (ArgumentError)


306
307
308
# File 'lib/attio/api_resource.rb', line 306

def validate_id!(id)
  raise ArgumentError, "ID is required" if id.nil? || id.to_s.empty?
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?

Comparison



160
161
162
# File 'lib/attio/api_resource.rb', line 160

def ==(other)
  other.is_a?(self.class) && id == other.id && @attributes == other.instance_variable_get(:@attributes)
end

#[](key) ⇒ Object

Attribute access

Parameters:

  • key (String, Symbol)

    The attribute key to retrieve

Returns:

  • (Object)

    The value of the attribute



44
45
46
# File 'lib/attio/api_resource.rb', line 44

def [](key)
  @attributes[key.to_sym]
end

#[]=(key, value) ⇒ Object

Set an attribute value and track changes

Parameters:

  • key (String, Symbol)

    The attribute key to set

  • value (Object)

    The value to set



51
52
53
54
55
56
57
58
59
60
# File 'lib/attio/api_resource.rb', line 51

def []=(key, value)
  key = key.to_sym
  old_value = @attributes[key]
  new_value = process_attribute_value(value)

  return if old_value == new_value

  @attributes[key] = new_value
  @changed_attributes.add(key)
end

#changedArray<String>

Get list of changed attribute names

Returns:

  • (Array<String>)

    Array of changed attribute names as strings



83
84
85
# File 'lib/attio/api_resource.rb', line 83

def changed
  @changed_attributes.map(&:to_s)
end

#changed?Boolean

Dirty tracking

Returns:

  • (Boolean)


77
78
79
# File 'lib/attio/api_resource.rb', line 77

def changed?
  !@changed_attributes.empty?
end

#changed_attributesHash

Get only the changed attributes and their new values

Returns:

  • (Hash)

    Hash of changed attributes with their current values



97
98
99
100
101
# File 'lib/attio/api_resource.rb', line 97

def changed_attributes
  @changed_attributes.each_with_object({}) do |key, hash|
    hash[key] = @attributes[key]
  end
end

#changesHash

Get changes with before and after values

Returns:

  • (Hash)

    Hash mapping attribute names to [old_value, new_value] arrays



89
90
91
92
93
# File 'lib/attio/api_resource.rb', line 89

def changes
  @changed_attributes.each_with_object({}) do |key, hash|
    hash[key.to_s] = [@original_attributes[key], @attributes[key]]
  end
end

#destroyObject Also known as: delete

Default destroy implementation



229
230
231
232
233
# File 'lib/attio/api_resource.rb', line 229

def destroy(**)
  raise InvalidRequestError, "Cannot destroy a resource without an ID" unless persisted?
  self.class.delete(id, **)
  true
end

#eachObject

Enumerable support



142
143
144
145
# File 'lib/attio/api_resource.rb', line 142

def each(&)
  return enum_for(:each) unless block_given?
  @attributes.each(&)
end

#fetch(key, default = nil) ⇒ Object

Fetch an attribute value with an optional default

Parameters:

  • key (String, Symbol)

    The attribute key to fetch

  • default (Object) (defaults to: nil)

    The default value if key is not found

Returns:

  • (Object)

    The attribute value or default



66
67
68
# File 'lib/attio/api_resource.rb', line 66

def fetch(key, default = nil)
  @attributes.fetch(key.to_sym, default)
end

#hashInteger

Generate hash code for use in Hash keys and Set members

Returns:

  • (Integer)

    Hash code based on class, ID, and attributes



167
168
169
# File 'lib/attio/api_resource.rb', line 167

def hash
  [self.class, id, @attributes].hash
end

#inspectString

Human-readable representation of the resource

Returns:

  • (String)

    Inspection string with class name, ID, and attributes



136
137
138
139
# File 'lib/attio/api_resource.rb', line 136

def inspect
  attrs = @attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
  "#<#{self.class.name}:#{object_id} id=#{id.inspect} #{attrs}>"
end

#key?(key) ⇒ Boolean Also known as: has_key?, include?

Returns:

  • (Boolean)


70
71
72
# File 'lib/attio/api_resource.rb', line 70

def key?(key)
  @attributes.key?(key.to_sym)
end

#keysArray<Symbol>

Get all attribute keys

Returns:

  • (Array<Symbol>)

    Array of attribute keys as symbols



149
150
151
# File 'lib/attio/api_resource.rb', line 149

def keys
  @attributes.keys
end

#persisted?Boolean

Check if resource has been persisted

Returns:

  • (Boolean)


180
181
182
# File 'lib/attio/api_resource.rb', line 180

def persisted?
  !id.nil?
end

#reset_changes!void

This method returns an undefined value.

Clear all tracked changes and update original attributes



105
106
107
108
# File 'lib/attio/api_resource.rb', line 105

def reset_changes!
  @changed_attributes.clear
  @original_attributes = deep_copy(@attributes)
end

#resource_pathString

Get the full API path for this specific resource instance

Returns:

  • (String)

    The full API path including the resource ID



215
216
217
# File 'lib/attio/api_resource.rb', line 215

def resource_path
  "#{self.class.resource_path}/#{id}"
end

#revert!void

This method returns an undefined value.

Revert all changes back to original attribute values



112
113
114
115
# File 'lib/attio/api_resource.rb', line 112

def revert!
  @attributes = deep_copy(@original_attributes)
  @changed_attributes.clear
end

#saveObject

Default save implementation



220
221
222
223
224
225
226
# File 'lib/attio/api_resource.rb', line 220

def save(**)
  if persisted?
    self.class.update(id, changed_attributes, **)
  else
    raise InvalidRequestError, "Cannot save a resource without an ID"
  end
end

#to_hObject Also known as: to_hash

Serialization



118
119
120
121
122
123
124
# File 'lib/attio/api_resource.rb', line 118

def to_h
  {
    id: id,
    created_at: created_at&.iso8601,
    **@attributes
  }.compact
end

#to_json(*opts) ⇒ String

Convert resource to JSON string

Parameters:

  • opts (Hash)

    Options to pass to JSON.generate

Returns:

  • (String)

    JSON representation of the resource



130
131
132
# File 'lib/attio/api_resource.rb', line 130

def to_json(*opts)
  JSON.generate(to_h, *opts)
end

#update_attributes(attributes) ⇒ Object

Update attributes



172
173
174
175
176
177
# File 'lib/attio/api_resource.rb', line 172

def update_attributes(attributes)
  attributes.each do |key, value|
    self[key] = value
  end
  self
end

#update_from(response) ⇒ Object

Update from API response



185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/attio/api_resource.rb', line 185

def update_from(response)
  normalized = normalize_attributes(response)
  @id = normalized[:id] if normalized[:id]
  @created_at = parse_timestamp(normalized[:created_at]) if normalized[:created_at]

  normalized.each do |key, value|
    next if SKIP_KEYS.include?(key)
    @attributes[key] = process_attribute_value(value)
  end

  reset_changes!
  self
end

#valuesArray

Get all attribute values

Returns:

  • (Array)

    Array of attribute values



155
156
157
# File 'lib/attio/api_resource.rb', line 155

def values
  @attributes.values
end