Class: BugBunny::Resource
- Inherits:
-
Object
- Object
- BugBunny::Resource
- Extended by:
- ActiveModel::Callbacks
- Includes:
- ActiveModel::API, ActiveModel::Attributes, ActiveModel::Dirty, ActiveModel::Validations
- Defined in:
- lib/bug_bunny/resource.rb
Overview
Clase base para modelos remotos que implementan **Active Record over AMQP (RESTful)**.
Soporta un esquema híbrido de datos y configuración de infraestructura en cascada:
-
Defaults: Definidos en la sesión.
-
Global: Definidos en BugBunny.configuration.
-
**Específico:** Definidos en la clase del recurso o vía ‘with`.
Defined Under Namespace
Classes: ScopeProxy
Configuración de Infraestructura Específica collapse
- .connection_pool ⇒ ConnectionPool?
- .exchange_options ⇒ Object writeonly
-
.param_key ⇒ String
Clave raíz para envolver el payload en las peticiones.
- .queue_options ⇒ Object writeonly
-
.resource_name ⇒ String
Nombre del recurso para la construcción de rutas.
Class Attribute Summary collapse
- .exchange ⇒ Object writeonly
- .exchange_type ⇒ Object writeonly
- .routing_key ⇒ Object writeonly
Instance Attribute Summary collapse
- #exchange ⇒ Object
-
#exchange_options ⇒ Hash
Opciones específicas de instancia para exchange y queue.
- #exchange_type ⇒ Object
- #persisted ⇒ Object
-
#queue_options ⇒ Hash
Opciones específicas de instancia para exchange y queue.
- #remote_attributes ⇒ Object readonly
- #routing_key ⇒ Object
Configuración de Infraestructura Específica collapse
-
.bug_bunny_client ⇒ BugBunny::Client
Instancia el cliente inyectando los middlewares núcleo y personalizados.
-
.calculate_routing_key(_id = nil) ⇒ String
Calcula la routing key final.
- .client_middleware(&block) ⇒ Object private
-
.current_exchange ⇒ String
Nombre del exchange actual.
-
.current_exchange_options ⇒ Hash
Opciones de exchange específicas (Nivel 3 de la cascada).
-
.current_exchange_type ⇒ String
Tipo de exchange (‘direct’, ‘topic’, ‘fanout’).
-
.current_queue_options ⇒ Hash
Opciones de cola específicas.
-
.resolve_config(key, instance_var) ⇒ Object?
Resuelve la configuración buscando en el hilo, luego en la jerarquía de clases.
- .resolve_middleware_stack ⇒ Object private
- .thread_config(key) ⇒ Object private
-
.with(exchange: nil, routing_key: nil, exchange_type: nil, pool: nil, exchange_options: nil, queue_options: nil) ⇒ Object
Permite configurar dinámicamente el contexto AMQP para una operación.
Acciones CRUD RESTful collapse
-
.all ⇒ Array<BugBunny::Resource>
Devuelve todos los registros.
-
.create(payload) ⇒ BugBunny::Resource
Crea una nueva instancia y la persiste.
-
.find(id) ⇒ BugBunny::Resource?
Busca un registro por ID (GET).
-
.where(filters = {}) ⇒ Array<BugBunny::Resource>
Realiza una búsqueda filtrada (GET).
Instancia collapse
-
#assign_attributes(new_attributes) ⇒ Object
Asignación masiva de atributos.
-
#attributes_for_serialization ⇒ Hash
Serialización combinada.
- #bug_bunny_client ⇒ BugBunny::Client
- #calculate_routing_key(id = nil) ⇒ String
-
#changed ⇒ Array<String>
Lista de atributos que han cambiado.
-
#changed? ⇒ Boolean
True si hay cambios nativos o dinámicos.
-
#changes_to_send ⇒ Hash
Retorna el hash combinado de cambios (Tipados + Dinámicos).
-
#clear_changes_information ⇒ Object
Limpia el rastreo de ActiveModel y nuestro rastreo dinámico interno.
- #current_exchange ⇒ String
- #current_exchange_type ⇒ String
-
#id ⇒ Object
Valor del ID buscando en múltiples nomenclaturas.
- #id=(value) ⇒ Object
-
#initialize(attributes = {}) ⇒ Resource
constructor
Inicializa el recurso.
-
#inspect ⇒ String
Representación legible del recurso.
-
#method_missing(method_name, *args, &block) ⇒ Object
Intercepta asignaciones dinámicas y las registra como cambios.
- #persisted? ⇒ Boolean
- #read_attribute_for_validation(attr) ⇒ Object
- #respond_to_missing?(method_name, include_private = false) ⇒ Boolean
-
#update(attributes) ⇒ Boolean
Actualiza y guarda.
Persistencia collapse
-
#destroy ⇒ Boolean
Elimina el recurso del servidor remoto (DELETE).
-
#save ⇒ Boolean
Guarda el recurso en el servidor remoto vía AMQP (POST o PUT).
Constructor Details
#initialize(attributes = {}) ⇒ Resource
Inicializa el recurso.
283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/bug_bunny/resource.rb', line 283 def initialize(attributes = {}) @extra_attributes = {}.with_indifferent_access @dynamic_changes = Set.new @persisted = false # Contexto de infraestructura @routing_key = self.class.thread_config(:routing_key) @exchange = self.class.thread_config(:exchange) @exchange_type = self.class.thread_config(:exchange_type) @exchange_options = self.class.thread_config(:exchange_options) || self.class. @queue_options = self.class.thread_config(:queue_options) || self.class. super end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_name, *args, &block) ⇒ Object
Intercepta asignaciones dinámicas y las registra como cambios.
380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
# File 'lib/bug_bunny/resource.rb', line 380 def method_missing(method_name, *args, &block) attribute_name = method_name.to_s if attribute_name.end_with?('=') key = attribute_name.chop val = args.first if @extra_attributes[key] != val @dynamic_changes << key @extra_attributes[key] = val end else @extra_attributes.key?(attribute_name) ? @extra_attributes[attribute_name] : super end end |
Class Attribute Details
.connection_pool ⇒ ConnectionPool?
63 64 65 |
# File 'lib/bug_bunny/resource.rb', line 63 def connection_pool resolve_config(:pool, :@connection_pool) end |
.exchange=(value) ⇒ Object (writeonly)
34 35 36 |
# File 'lib/bug_bunny/resource.rb', line 34 def exchange=(value) @exchange = value end |
.exchange_options=(value) ⇒ Object (writeonly)
37 38 39 |
# File 'lib/bug_bunny/resource.rb', line 37 def (value) @exchange_options = value end |
.exchange_type=(value) ⇒ Object (writeonly)
34 35 36 |
# File 'lib/bug_bunny/resource.rb', line 34 def exchange_type=(value) @exchange_type = value end |
.param_key ⇒ String
Returns Clave raíz para envolver el payload en las peticiones.
93 94 95 |
# File 'lib/bug_bunny/resource.rb', line 93 def param_key resolve_config(:param_key, :@param_key) || model_name.element end |
.queue_options=(value) ⇒ Object (writeonly)
37 38 39 |
# File 'lib/bug_bunny/resource.rb', line 37 def (value) @queue_options = value end |
.resource_name ⇒ String
Returns Nombre del recurso para la construcción de rutas.
88 89 90 |
# File 'lib/bug_bunny/resource.rb', line 88 def resource_name resolve_config(:resource_name, :@resource_name) || name.demodulize.underscore.pluralize end |
.routing_key=(value) ⇒ Object (writeonly)
34 35 36 |
# File 'lib/bug_bunny/resource.rb', line 34 def routing_key=(value) @routing_key = value end |
Instance Attribute Details
#exchange ⇒ Object
28 29 30 |
# File 'lib/bug_bunny/resource.rb', line 28 def exchange @exchange end |
#exchange_options ⇒ Hash
Returns Opciones específicas de instancia para exchange y queue.
31 32 33 |
# File 'lib/bug_bunny/resource.rb', line 31 def @exchange_options end |
#exchange_type ⇒ Object
28 29 30 |
# File 'lib/bug_bunny/resource.rb', line 28 def exchange_type @exchange_type end |
#persisted ⇒ Object
28 29 30 |
# File 'lib/bug_bunny/resource.rb', line 28 def persisted @persisted end |
#queue_options ⇒ Hash
Returns Opciones específicas de instancia para exchange y queue.
31 32 33 |
# File 'lib/bug_bunny/resource.rb', line 31 def @queue_options end |
#remote_attributes ⇒ Object (readonly)
27 28 29 |
# File 'lib/bug_bunny/resource.rb', line 27 def remote_attributes @remote_attributes end |
#routing_key ⇒ Object
28 29 30 |
# File 'lib/bug_bunny/resource.rb', line 28 def routing_key @routing_key end |
Class Method Details
.all ⇒ Array<BugBunny::Resource>
Devuelve todos los registros.
236 237 238 |
# File 'lib/bug_bunny/resource.rb', line 236 def all where({}) end |
.bug_bunny_client ⇒ BugBunny::Client
Instancia el cliente inyectando los middlewares núcleo y personalizados. Integra automáticamente ‘RaiseError` y `JsonResponse` para que el ORM trabaje puramente con datos parseados o atrape excepciones sin validar HTTP Status manuales.
120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/bug_bunny/resource.rb', line 120 def bug_bunny_client pool = connection_pool raise BugBunny::Error, "Connection pool missing for #{name}" unless pool BugBunny::Client.new(pool: pool) do |stack| # 1. Middlewares Core (Siempre presentes para el Resource) stack.use BugBunny::Middleware::RaiseError stack.use BugBunny::Middleware::JsonResponse # 2. Middlewares Personalizados del Usuario resolve_middleware_stack.each { |block| block.call(stack) } end end |
.calculate_routing_key(_id = nil) ⇒ String
Calcula la routing key final.
191 192 193 194 195 196 197 198 199 |
# File 'lib/bug_bunny/resource.rb', line 191 def calculate_routing_key(_id = nil) manual_rk = thread_config(:routing_key) return manual_rk if manual_rk static_rk = resolve_config(:routing_key, :@routing_key) return static_rk if static_rk.present? resource_name end |
.client_middleware(&block) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
98 99 100 101 |
# File 'lib/bug_bunny/resource.rb', line 98 def client_middleware(&block) @client_middleware_stack ||= [] @client_middleware_stack << block end |
.create(payload) ⇒ BugBunny::Resource
Crea una nueva instancia y la persiste.
272 273 274 275 276 |
# File 'lib/bug_bunny/resource.rb', line 272 def create(payload) instance = new(payload) instance.save instance end |
.current_exchange ⇒ String
Returns Nombre del exchange actual.
68 69 70 |
# File 'lib/bug_bunny/resource.rb', line 68 def current_exchange resolve_config(:exchange, :@exchange) || raise(ArgumentError, "Exchange not defined for #{name}") end |
.current_exchange_options ⇒ Hash
Returns Opciones de exchange específicas (Nivel 3 de la cascada).
78 79 80 |
# File 'lib/bug_bunny/resource.rb', line 78 def resolve_config(:exchange_options, :@exchange_options) || {} end |
.current_exchange_type ⇒ String
Returns Tipo de exchange (‘direct’, ‘topic’, ‘fanout’).
73 74 75 |
# File 'lib/bug_bunny/resource.rb', line 73 def current_exchange_type resolve_config(:exchange_type, :@exchange_type) || 'direct' end |
.current_queue_options ⇒ Hash
Returns Opciones de cola específicas.
83 84 85 |
# File 'lib/bug_bunny/resource.rb', line 83 def resolve_config(:queue_options, :@queue_options) || {} end |
.find(id) ⇒ BugBunny::Resource?
Busca un registro por ID (GET). Mapea un 404 (NotFound) devolviendo un objeto nulo.
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/bug_bunny/resource.rb', line 245 def find(id) rk = calculate_routing_key(id) path = "#{resource_name}/#{id}" response = bug_bunny_client.request( path, method: :get, exchange: current_exchange, exchange_type: current_exchange_type, routing_key: rk, exchange_options: , queue_options: ) return nil unless response && response['body'].is_a?(Hash) instance = new(response['body']) instance.persisted = true instance.send(:clear_changes_information) instance rescue BugBunny::NotFound nil end |
.resolve_config(key, instance_var) ⇒ Object?
Resuelve la configuración buscando en el hilo, luego en la jerarquía de clases.
48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/bug_bunny/resource.rb', line 48 def resolve_config(key, instance_var) val = thread_config(key) return val if val target = self while target <= BugBunny::Resource value = target.instance_variable_get(instance_var) return value.respond_to?(:call) ? value.call : value unless value.nil? target = target.superclass end nil end |
.resolve_middleware_stack ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
104 105 106 107 108 109 110 111 112 113 |
# File 'lib/bug_bunny/resource.rb', line 104 def resolve_middleware_stack stack = [] target = self while target <= BugBunny::Resource middlewares = target.instance_variable_get(:@client_middleware_stack) stack.unshift(*middlewares) if middlewares target = target.superclass end stack end |
.thread_config(key) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
40 41 42 |
# File 'lib/bug_bunny/resource.rb', line 40 def thread_config(key) Thread.current["bb_#{object_id}_#{key}"] end |
.where(filters = {}) ⇒ Array<BugBunny::Resource>
Realiza una búsqueda filtrada (GET). Mapea un posible 404 a un array vacío.
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/bug_bunny/resource.rb', line 208 def where(filters = {}) rk = calculate_routing_key response = bug_bunny_client.request( resource_name, method: :get, exchange: current_exchange, exchange_type: current_exchange_type, routing_key: rk, exchange_options: , queue_options: , params: filters.presence || {} ) return [] unless response['body'].is_a?(Array) response['body'].map do |attrs| inst = new(attrs) inst.persisted = true inst.send(:clear_changes_information) inst end rescue BugBunny::NotFound [] end |
.with(exchange: nil, routing_key: nil, exchange_type: nil, pool: nil, exchange_options: nil, queue_options: nil) ⇒ Object
Permite configurar dinámicamente el contexto AMQP para una operación.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/bug_bunny/resource.rb', line 142 def with(exchange: nil, routing_key: nil, exchange_type: nil, pool: nil, exchange_options: nil, queue_options: nil) keys = { exchange: "bb_#{object_id}_exchange", exchange_type: "bb_#{object_id}_exchange_type", pool: "bb_#{object_id}_pool", routing_key: "bb_#{object_id}_routing_key", exchange_options: "bb_#{object_id}_exchange_options", queue_options: "bb_#{object_id}_queue_options" } old_values = {} keys.each { |k, v| old_values[k] = Thread.current[v] } Thread.current[keys[:exchange]] = exchange if exchange Thread.current[keys[:exchange_type]] = exchange_type if exchange_type Thread.current[keys[:pool]] = pool if pool Thread.current[keys[:routing_key]] = routing_key if routing_key Thread.current[keys[:exchange_options]] = if Thread.current[keys[:queue_options]] = if if block_given? begin; yield; ensure; keys.each { |k, v| Thread.current[v] = old_values[k] }; end else ScopeProxy.new(self, keys, old_values) end end |
Instance Method Details
#assign_attributes(new_attributes) ⇒ Object
Asignación masiva de atributos.
347 348 349 350 351 |
# File 'lib/bug_bunny/resource.rb', line 347 def assign_attributes(new_attributes) return if new_attributes.nil? new_attributes.each { |k, v| public_send("#{k}=", v) } end |
#attributes_for_serialization ⇒ Hash
Serialización combinada.
316 317 318 |
# File 'lib/bug_bunny/resource.rb', line 316 def attributes_for_serialization @extra_attributes.merge(attributes) end |
#bug_bunny_client ⇒ BugBunny::Client
336 337 338 |
# File 'lib/bug_bunny/resource.rb', line 336 def bug_bunny_client self.class.bug_bunny_client end |
#calculate_routing_key(id = nil) ⇒ String
321 322 323 |
# File 'lib/bug_bunny/resource.rb', line 321 def calculate_routing_key(id = nil) @routing_key || self.class.calculate_routing_key(id) end |
#changed ⇒ Array<String>
Returns Lista de atributos que han cambiado.
310 311 312 |
# File 'lib/bug_bunny/resource.rb', line 310 def changed (super + @dynamic_changes.to_a).uniq end |
#changed? ⇒ Boolean
Returns true si hay cambios nativos o dinámicos.
305 306 307 |
# File 'lib/bug_bunny/resource.rb', line 305 def changed? super || @dynamic_changes.any? end |
#changes_to_send ⇒ Hash
Retorna el hash combinado de cambios (Tipados + Dinámicos).
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 |
# File 'lib/bug_bunny/resource.rb', line 363 def changes_to_send # 1. Obtener los nombres de todos los atributos que han cambiado (incluyendo dinámicos vía attribute_will_change!) changed_keys = changed # 2. Construir el payload con los valores actuales de esas keys payload = {} changed_keys.each do |key| payload[key] = public_send(key) end return payload unless payload.empty? # Fallback: Si no hay cambios detectados (ej: en un create), enviamos todo attributes_for_serialization.except('id', 'ID', 'Id', '_id') end |
#clear_changes_information ⇒ Object
Limpia el rastreo de ActiveModel y nuestro rastreo dinámico interno.
299 300 301 302 |
# File 'lib/bug_bunny/resource.rb', line 299 def clear_changes_information super @dynamic_changes.clear end |
#current_exchange ⇒ String
326 327 328 |
# File 'lib/bug_bunny/resource.rb', line 326 def current_exchange @exchange || self.class.current_exchange end |
#current_exchange_type ⇒ String
331 332 333 |
# File 'lib/bug_bunny/resource.rb', line 331 def current_exchange_type @exchange_type || self.class.current_exchange_type end |
#destroy ⇒ Boolean
Elimina el recurso del servidor remoto (DELETE).
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 |
# File 'lib/bug_bunny/resource.rb', line 473 def destroy return false unless persisted? return false unless id run_callbacks(:destroy) do path = "#{self.class.resource_name}/#{id}" rk = calculate_routing_key(id) bug_bunny_client.request( path, method: :delete, exchange: current_exchange, exchange_type: current_exchange_type, routing_key: rk, exchange_options: @exchange_options, queue_options: @queue_options ) self.persisted = false end true rescue BugBunny::UnprocessableEntity => e load_remote_rabbit_errors(e.) false rescue BugBunny::ClientError => e load_remote_rabbit_errors(e.) false rescue BugBunny::ServerError false end |
#id ⇒ Object
Returns Valor del ID buscando en múltiples nomenclaturas.
400 401 402 |
# File 'lib/bug_bunny/resource.rb', line 400 def id attributes['id'] || @extra_attributes['id'] || @extra_attributes['ID'] || @extra_attributes['Id'] || @extra_attributes['_id'] end |
#id=(value) ⇒ Object
415 416 417 418 419 420 421 422 |
# File 'lib/bug_bunny/resource.rb', line 415 def id=(value) if self.class.attribute_names.include?('id') super else @dynamic_changes << 'id' if @extra_attributes['id'] != value @extra_attributes['id'] = value end end |
#inspect ⇒ String
Representación legible del recurso. Muestra solo ID y atributos principales, sin detalles de infraestructura.
408 409 410 411 412 413 |
# File 'lib/bug_bunny/resource.rb', line 408 def inspect infra_keys = %w[routing_key exchange exchange_type exchange_options queue_options _id] attrs = @extra_attributes.merge(attributes).reject { |k, _| infra_keys.include?(k) || k == 'id' } attr_str = attrs.first(5).map { |k, v| "#{k}=#{v.inspect}" }.join(' ') "#<#{self.class.name} id=#{id.inspect} persisted=#{@persisted}#{" #{attr_str}" unless attr_str.empty?}>" end |
#persisted? ⇒ Boolean
341 342 343 |
# File 'lib/bug_bunny/resource.rb', line 341 def persisted? !!@persisted end |
#read_attribute_for_validation(attr) ⇒ Object
424 425 426 427 |
# File 'lib/bug_bunny/resource.rb', line 424 def read_attribute_for_validation(attr) attr_s = attr.to_s self.class.attribute_names.include?(attr_s) ? attribute(attr_s) : @extra_attributes[attr_s] end |
#respond_to_missing?(method_name, include_private = false) ⇒ Boolean
395 396 397 |
# File 'lib/bug_bunny/resource.rb', line 395 def respond_to_missing?(method_name, include_private = false) @extra_attributes.key?(method_name.to_s.sub(/=$/, '')) || super end |
#save ⇒ Boolean
Guarda el recurso en el servidor remoto vía AMQP (POST o PUT). Asume el Happy Path; el middleware se encarga de interceptar y lanzar excepciones.
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 |
# File 'lib/bug_bunny/resource.rb', line 435 def save return false unless valid? run_callbacks(:save) do is_new = !persisted? rk = calculate_routing_key(id) flat_payload = changes_to_send key = self.class.param_key wrapped_payload = { key => flat_payload } path = is_new ? self.class.resource_name : "#{self.class.resource_name}/#{id}" method = is_new ? :post : :put # Si el middleware de errores no lanza excepción, asumimos un éxito (200..299) response = bug_bunny_client.request( path, method: method, exchange: current_exchange, exchange_type: current_exchange_type, routing_key: rk, exchange_options: @exchange_options, queue_options: @queue_options, body: wrapped_payload ) assign_attributes(response['body']) self.persisted = true clear_changes_information true end rescue BugBunny::UnprocessableEntity => e load_remote_rabbit_errors(e.) false end |
#update(attributes) ⇒ Boolean
Actualiza y guarda.
356 357 358 359 |
# File 'lib/bug_bunny/resource.rb', line 356 def update(attributes) assign_attributes(attributes) save end |