Class: MOCO::BaseEntity
- Inherits:
-
Object
- Object
- MOCO::BaseEntity
- Defined in:
- lib/moco/entities.rb,
lib/moco/entities/base_entity.rb
Overview
Base class for all MOCO API entities
Direct Known Subclasses
Activity, Company, Customer, Deal, Expense, Holiday, Invoice, PlanningEntry, Presence, Project, Schedule, Task, User, WebHook
Instance Attribute Summary collapse
-
#attributes ⇒ Object
readonly
Returns the value of attribute attributes.
-
#client ⇒ Object
readonly
Returns the value of attribute client.
Instance Method Summary collapse
-
#==(other) ⇒ Object
Compares two entities based on class and ID.
-
#association(association_name, target_class_name_override = nil) ⇒ Object
Helper method to fetch associated objects based on data in attributes.
-
#destroy ⇒ Object
Deletes the entity from the API.
- #eql?(other) ⇒ Boolean
-
#has_many(association_name, foreign_key = nil, target_class_name_override = nil, nested = false) ⇒ Object
Helper method to fetch associated collections based on a foreign key.
- #hash ⇒ Object
-
#id ⇒ Object
Returns the entity’s ID.
-
#initialize(client, response_data) ⇒ BaseEntity
constructor
Initializes an entity instance from raw API response data (Hash).
-
#inspect ⇒ Object
Provides a string representation of the entity.
-
#reload ⇒ Object
Reloads the entity from the API.
-
#save ⇒ Object
Saves changes to the entity back to the API.
-
#to_h ⇒ Object
Converts the entity to a Hash, recursively converting nested entities.
-
#to_json(*options) ⇒ Object
Converts the entity to a JSON string.
-
#to_s ⇒ Object
Provides a basic string representation (can be overridden by subclasses).
-
#update(new_attributes) ⇒ Object
Updates attributes and saves the entity in one step.
Constructor Details
#initialize(client, response_data) ⇒ BaseEntity
Initializes an entity instance from raw API response data (Hash). Recursively processes nested hashes and arrays, converting known entity structures into corresponding MOCO::Entity instances.
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/moco/entities/base_entity.rb', line 13 def initialize(client, response_data) @client = client # Ensure response_data is a Hash before proceeding unless response_data.is_a?(Hash) raise ArgumentError, "BaseEntity must be initialized with a Hash, got: #{response_data.class}" end # Process the top-level hash: transform keys and process nested values. @attributes = response_data.transform_keys(&:to_sym) .each_with_object({}) do |(k, v), acc| # Use process_value only for the *values* within the hash acc[k] = process_value(v, k) end # Define attribute methods based on the processed attributes hash define_attribute_methods end |
Instance Attribute Details
#attributes ⇒ Object (readonly)
Returns the value of attribute attributes.
8 9 10 |
# File 'lib/moco/entities/base_entity.rb', line 8 def attributes @attributes end |
#client ⇒ Object (readonly)
Returns the value of attribute client.
8 9 10 |
# File 'lib/moco/entities/base_entity.rb', line 8 def client @client end |
Instance Method Details
#==(other) ⇒ Object
Compares two entities based on class and ID.
43 44 45 |
# File 'lib/moco/entities/base_entity.rb', line 43 def ==(other) id == other.id end |
#association(association_name, target_class_name_override = nil) ⇒ Object
Helper method to fetch associated objects based on data in attributes. Uses memoization to avoid repeated API calls. association_name: Symbol representing the association (e.g., :project, :customer). target_class_name_override: String specifying the target class if it differs
from the classified association name (e.g., "Company" for :customer).
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/moco/entities/base_entity.rb', line 148 def association(association_name, target_class_name_override = nil) # Initialize cache if it doesn't exist @_association_cache ||= {} # Return cached object if available return @_association_cache[association_name] if @_association_cache.key?(association_name) association_data = attributes[association_name] # If data is already a BaseEntity object (processed during initialization), use it directly. return @_association_cache[association_name] = association_data if association_data.is_a?(MOCO::BaseEntity) # If data is a hash containing an ID, fetch the object. if association_data.is_a?(Hash) && association_data[:id] assoc_id = association_data[:id] target_class_name = target_class_name_override || ActiveSupport::Inflector.classify(association_name.to_s) collection_name = ActiveSupport::Inflector.tableize(target_class_name).to_sym # e.g., "Project" -> :projects # Check if the client responds to the collection method (e.g., client.projects) if client.respond_to?(collection_name) # Fetch the object using the appropriate collection proxy fetched_object = client.send(collection_name).find(assoc_id) return @_association_cache[association_name] = fetched_object else warn "Warning: Client does not respond to collection '#{collection_name}' for association '#{association_name}'." return @_association_cache[association_name] = nil end end # If data is not an object or a hash with an ID, return nil. @_association_cache[association_name] = nil end |
#destroy ⇒ Object
Deletes the entity from the API. Returns true on success, false on failure.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/moco/entities/base_entity.rb', line 104 def destroy return false if id.nil? # Can't destroy without an ID # Determine the collection name from the class name collection_name = ActiveSupport::Inflector.tableize(self.class.name.split("::").last).to_sym # Check if the client responds to the collection method if client.respond_to?(collection_name) # Use the collection proxy to delete the entity client.send(collection_name).delete(id) true else warn "Warning: Client does not respond to collection '#{collection_name}' for destroying entity." false end end |
#eql?(other) ⇒ Boolean
9 10 11 12 13 |
# File 'lib/moco/entities.rb', line 9 def eql?(other) return false unless other.is_a? self.class id == other.id end |
#has_many(association_name, foreign_key = nil, target_class_name_override = nil, nested = false) ⇒ Object
Helper method to fetch associated collections based on a foreign key. Uses memoization to avoid repeated API calls. association_name: Symbol representing the association (e.g., :tasks, :activities). foreign_key: Symbol representing the foreign key to use (e.g., :project_id). target_class_name_override: String specifying the target class if it differs
from the classified association name (e.g., "Task" for :tasks).
nested: Boolean indicating if this is a nested resource (e.g., project.tasks)
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/moco/entities/base_entity.rb', line 187 def has_many(association_name, foreign_key = nil, target_class_name_override = nil, nested = false) # Initialize cache if it doesn't exist @_association_cache ||= {} # Return cached collection if available return @_association_cache[association_name] if @_association_cache.key?(association_name) # If the association data is already in attributes and is an array of entities # AND this is NOT a nested resource, use it directly # For nested resources, we always create a proxy to ensure CRUD operations work association_data = attributes[association_name] if !nested && association_data.is_a?(Array) && association_data.all? { |item| item.is_a?(MOCO::BaseEntity) } return @_association_cache[association_name] = association_data end # Otherwise, fetch the collection from the API target_class_name = target_class_name_override || ActiveSupport::Inflector.classify(association_name.to_s) collection_name = ActiveSupport::Inflector.tableize(target_class_name).to_sym # e.g., "Task" -> :tasks # Determine the foreign key to use fk = foreign_key || :"#{ActiveSupport::Inflector.underscore(self.class.name.split("::").last)}_id" # Check if this is a nested resource if nested # For nested resources, create a NestedCollectionProxy @_association_cache[association_name] = MOCO::NestedCollectionProxy.new( client, self, collection_name, target_class_name ) elsif client.respond_to?(collection_name) # For regular resources, use the standard collection proxy with a filter @_association_cache[association_name] = client.send(collection_name).where(fk => id) else warn "Warning: Client does not respond to collection '#{collection_name}' for association '#{association_name}'." @_association_cache[association_name] = [] end end |
#hash ⇒ Object
15 16 17 |
# File 'lib/moco/entities.rb', line 15 def hash id.hash end |
#id ⇒ Object
Returns the entity’s ID.
38 39 40 |
# File 'lib/moco/entities/base_entity.rb', line 38 def id attributes[:id] || attributes["id"] end |
#inspect ⇒ Object
Provides a string representation of the entity.
64 65 66 |
# File 'lib/moco/entities/base_entity.rb', line 64 def inspect "#<#{self.class.name}:#{object_id} @attributes=#{@attributes.inspect}>" end |
#reload ⇒ Object
Reloads the entity from the API. Returns self on success for method chaining.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/moco/entities/base_entity.rb', line 123 def reload return self if id.nil? # Can't reload without an ID # Determine the collection name from the class name collection_name = ActiveSupport::Inflector.tableize(self.class.name.split("::").last).to_sym # Check if the client responds to the collection method if client.respond_to?(collection_name) # Use the collection proxy to find the entity reloaded = client.send(collection_name).find(id) # Update attributes with the reloaded data @attributes = reloaded.attributes if reloaded else warn "Warning: Client does not respond to collection '#{collection_name}' for reloading entity." end self # Return self for method chaining end |
#save ⇒ Object
Saves changes to the entity back to the API. Returns self on success for method chaining.
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/moco/entities/base_entity.rb', line 70 def save return self if id.nil? # Can't save without an ID # Determine the collection name from the class name collection_name = ActiveSupport::Inflector.tableize(self.class.name.split("::").last).to_sym # Check if the client responds to the collection method if client.respond_to?(collection_name) # Use the collection proxy to update the entity updated_data = client.send(collection_name).update(id, attributes) # Update local attributes with the response data @attributes = updated_data.attributes if updated_data else warn "Warning: Client does not respond to collection '#{collection_name}' for saving entity." end self # Return self for method chaining end |
#to_h ⇒ Object
Converts the entity to a Hash, recursively converting nested entities.
48 49 50 51 52 53 54 55 |
# File 'lib/moco/entities/base_entity.rb', line 48 def to_h hash = {} instance_variables.each do |var| key = var.to_s.delete_prefix("@") hash[key.to_sym] = instance_variable_get(var) end hash end |
#to_json(*options) ⇒ Object
Converts the entity to a JSON string.
59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/moco/entities/base_entity.rb', line 59 def to_json(*arg) to_h do |k, v| if v.is_a? Hash if v.key?(:id) && !v[:id].nil? ["#{k}_id", v[:id]] else [k, v.except(:id)] end else [k, v] end end.to_h.to_json(arg) end |
#to_s ⇒ Object
Provides a basic string representation (can be overridden by subclasses).
33 34 35 |
# File 'lib/moco/entities/base_entity.rb', line 33 def to_s "#{self.class.name.split("::").last} ##{id}" end |
#update(new_attributes) ⇒ Object
Updates attributes and saves the entity in one step. Returns self on success for method chaining.
92 93 94 95 96 97 98 99 100 |
# File 'lib/moco/entities/base_entity.rb', line 92 def update(new_attributes) # Update attributes new_attributes.each do |key, value| send("#{key}=", value) if respond_to?("#{key}=") end # Save changes save end |