Class: Attio::APIResource
- Inherits:
-
Object
- Object
- Attio::APIResource
- 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
Direct Known Subclasses
Attribute, Comment, Entry, Internal::Record, List, Meta, Note, Object, Task, Thread, Webhook, WorkspaceMember
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
-
#created_at ⇒ Object
readonly
Returns the value of attribute created_at.
-
#id ⇒ Object
readonly
Returns the value of attribute id.
-
#metadata ⇒ Object
readonly
Returns the value of attribute metadata.
Class Method Summary collapse
-
.api_operations(*operations) ⇒ Object
Define which operations this resource supports Example: api_operations :list, :create, :retrieve, :update, :delete.
-
.attr_attio(*attributes) ⇒ Object
Define attribute accessors for known attributes.
-
.execute_request(method, path, params = {}, opts = {}) ⇒ Object
Execute HTTP request.
-
.id_param_name(id = nil) ⇒ Object
Get the ID parameter name (usually "id", but sometimes needs prefix).
-
.prepare_params_for_create(params) ⇒ Object
Hook for subclasses to prepare params before create.
-
.prepare_params_for_update(params) ⇒ Object
Hook for subclasses to prepare params before update.
-
.resource_name ⇒ String
Get the resource name derived from the class name.
-
.resource_path ⇒ String
Resource path helpers Get the base API path for this resource type.
-
.validate_id!(id) ⇒ Object
Validate an ID parameter.
Instance Method Summary collapse
-
#==(other) ⇒ Object
(also: #eql?)
Comparison.
-
#[](key) ⇒ Object
Attribute access.
-
#[]=(key, value) ⇒ Object
Set an attribute value and track changes.
-
#changed ⇒ Array<String>
Get list of changed attribute names.
-
#changed? ⇒ Boolean
Dirty tracking.
-
#changed_attributes ⇒ Hash
Get only the changed attributes and their new values.
-
#changes ⇒ Hash
Get changes with before and after values.
-
#destroy ⇒ Object
(also: #delete)
Default destroy implementation.
-
#each ⇒ Object
Enumerable support.
-
#fetch(key, default = nil) ⇒ Object
Fetch an attribute value with an optional default.
-
#hash ⇒ Integer
Generate hash code for use in Hash keys and Set members.
-
#initialize(attributes = {}, opts = {}) ⇒ APIResource
constructor
A new instance of APIResource.
-
#inspect ⇒ String
Human-readable representation of the resource.
- #key?(key) ⇒ Boolean (also: #has_key?, #include?)
-
#keys ⇒ Array<Symbol>
Get all attribute keys.
-
#persisted? ⇒ Boolean
Check if resource has been persisted.
-
#reset_changes! ⇒ void
Clear all tracked changes and update original attributes.
-
#resource_path ⇒ String
Get the full API path for this specific resource instance.
-
#revert! ⇒ void
Revert all changes back to original attribute values.
-
#save ⇒ Object
Default save implementation.
-
#to_h ⇒ Object
(also: #to_hash)
Serialization.
-
#to_json(*opts) ⇒ String
Convert resource to JSON string.
-
#update_attributes(attributes) ⇒ Object
Update attributes.
-
#update_from(response) ⇒ Object
Update from API response.
-
#values ⇒ Array
Get all attribute values.
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 = (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_at ⇒ Object (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 |
#id ⇒ Object (readonly)
Returns the value of attribute id.
9 10 11 |
# File 'lib/attio/api_resource.rb', line 9 def id @id end |
#metadata ⇒ Object (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_name ⇒ String
Get the resource name derived from the class name
209 210 211 |
# File 'lib/attio/api_resource.rb', line 209 def self.resource_name name.split("::").last.downcase end |
.resource_path ⇒ String
Resource path helpers Get the base API path for this resource type
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
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
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
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 |
#changed ⇒ Array<String>
Get list of changed attribute names
83 84 85 |
# File 'lib/attio/api_resource.rb', line 83 def changed @changed_attributes.map(&:to_s) end |
#changed? ⇒ Boolean
Dirty tracking
77 78 79 |
# File 'lib/attio/api_resource.rb', line 77 def changed? !@changed_attributes.empty? end |
#changed_attributes ⇒ Hash
Get only the changed attributes and their new 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 |
#changes ⇒ Hash
Get changes with before and after values
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 |
#destroy ⇒ Object 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 |
#each ⇒ Object
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
66 67 68 |
# File 'lib/attio/api_resource.rb', line 66 def fetch(key, default = nil) @attributes.fetch(key.to_sym, default) end |
#hash ⇒ Integer
Generate hash code for use in Hash keys and Set members
167 168 169 |
# File 'lib/attio/api_resource.rb', line 167 def hash [self.class, id, @attributes].hash end |
#inspect ⇒ String
Human-readable representation of the resource
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?
70 71 72 |
# File 'lib/attio/api_resource.rb', line 70 def key?(key) @attributes.key?(key.to_sym) end |
#keys ⇒ Array<Symbol>
Get all attribute keys
149 150 151 |
# File 'lib/attio/api_resource.rb', line 149 def keys @attributes.keys end |
#persisted? ⇒ Boolean
Check if resource has been persisted
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_path ⇒ String
Get the full API path for this specific resource instance
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 |
#save ⇒ Object
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_h ⇒ Object 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
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 = (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 |
#values ⇒ Array
Get all attribute values
155 156 157 |
# File 'lib/attio/api_resource.rb', line 155 def values @attributes.values end |