bug_bunny 3.0.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +166 -197
- data/lib/bug_bunny/config.rb +10 -0
- data/lib/bug_bunny/consumer.rb +68 -44
- data/lib/bug_bunny/controller.rb +131 -57
- data/lib/bug_bunny/producer.rb +10 -2
- data/lib/bug_bunny/resource.rb +133 -33
- data/lib/bug_bunny/version.rb +1 -1
- data/lib/bug_bunny.rb +1 -1
- metadata +21 -19
data/lib/bug_bunny/resource.rb
CHANGED
|
@@ -9,15 +9,21 @@ module BugBunny
|
|
|
9
9
|
# Esta clase transforma operaciones CRUD estándar en peticiones RPC utilizando
|
|
10
10
|
# verbos HTTP semánticos (GET, POST, PUT, DELETE) transportados sobre headers AMQP.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
12
|
+
# También gestiona la serialización automática de parámetros ("wrapping") para
|
|
13
|
+
# compatibilidad con Strong Parameters de Rails.
|
|
14
|
+
#
|
|
15
|
+
# @example Definición de un recurso
|
|
13
16
|
# class User < BugBunny::Resource
|
|
14
17
|
# self.exchange = 'app.topic'
|
|
15
18
|
# self.resource_name = 'users'
|
|
19
|
+
# # Opcional: Personalizar la clave raíz del JSON
|
|
20
|
+
# self.param_key = 'user_data'
|
|
16
21
|
# end
|
|
17
22
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
23
|
+
# @example Uso con contexto temporal
|
|
24
|
+
# # La instancia 'user' recordará que debe usar la routing_key 'urgent'
|
|
25
|
+
# user = User.with(routing_key: 'urgent').new(name: 'Gaby')
|
|
26
|
+
# user.save # Enviará a la cola 'urgent' aunque estemos fuera del bloque .with
|
|
21
27
|
class Resource
|
|
22
28
|
include ActiveModel::API
|
|
23
29
|
include ActiveModel::Dirty
|
|
@@ -26,17 +32,39 @@ module BugBunny
|
|
|
26
32
|
|
|
27
33
|
define_model_callbacks :save, :create, :update, :destroy
|
|
28
34
|
|
|
35
|
+
# @return [HashWithIndifferentAccess] Contenedor de los atributos remotos (JSON crudo).
|
|
29
36
|
attr_reader :remote_attributes
|
|
37
|
+
|
|
38
|
+
# @return [Boolean] Indica si el objeto ha sido guardado en el servicio remoto.
|
|
30
39
|
attr_accessor :persisted
|
|
31
40
|
|
|
41
|
+
# @return [String, nil] Routing Key capturada en el momento de la instanciación.
|
|
42
|
+
attr_accessor :routing_key
|
|
43
|
+
|
|
44
|
+
# @return [String, nil] Exchange capturado en el momento de la instanciación.
|
|
45
|
+
attr_accessor :exchange
|
|
46
|
+
|
|
47
|
+
# @return [String, nil] Tipo de Exchange capturado en el momento de la instanciación.
|
|
48
|
+
attr_accessor :exchange_type
|
|
49
|
+
|
|
32
50
|
class << self
|
|
33
|
-
|
|
51
|
+
# Configuración heredable
|
|
52
|
+
attr_writer :connection_pool, :exchange, :exchange_type, :resource_name, :routing_key, :param_key
|
|
34
53
|
|
|
35
|
-
#
|
|
54
|
+
# Lee la configuración del Thread actual (usado por el scope .with).
|
|
55
|
+
# @api private
|
|
56
|
+
def thread_config(key)
|
|
57
|
+
Thread.current["bb_#{object_id}_#{key}"]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Resuelve la configuración buscando en: 1. Thread (Scope), 2. Clase, 3. Herencia.
|
|
36
61
|
# @api private
|
|
37
62
|
def resolve_config(key, instance_var)
|
|
38
|
-
|
|
39
|
-
|
|
63
|
+
# 1. Prioridad: Contexto de hilo (.with)
|
|
64
|
+
val = thread_config(key)
|
|
65
|
+
return val if val
|
|
66
|
+
|
|
67
|
+
# 2. Prioridad: Jerarquía de clases
|
|
40
68
|
target = self
|
|
41
69
|
while target <= BugBunny::Resource
|
|
42
70
|
value = target.instance_variable_get(instance_var)
|
|
@@ -46,15 +74,32 @@ module BugBunny
|
|
|
46
74
|
nil
|
|
47
75
|
end
|
|
48
76
|
|
|
77
|
+
# @return [ConnectionPool] El pool de conexiones asignado.
|
|
49
78
|
def connection_pool; resolve_config(:pool, :@connection_pool); end
|
|
79
|
+
|
|
80
|
+
# @return [String] El exchange configurado.
|
|
81
|
+
# @raise [ArgumentError] Si no se ha definido un exchange.
|
|
50
82
|
def current_exchange; resolve_config(:exchange, :@exchange) || raise(ArgumentError, "Exchange not defined"); end
|
|
83
|
+
|
|
84
|
+
# @return [String] El tipo de exchange (default: direct).
|
|
51
85
|
def current_exchange_type; resolve_config(:exchange_type, :@exchange_type) || 'direct'; end
|
|
52
|
-
|
|
86
|
+
|
|
87
|
+
# @return [String] El nombre del recurso (ej: 'users'). Se infiere del nombre de la clase si no existe.
|
|
53
88
|
def resource_name
|
|
54
89
|
resolve_config(:resource_name, :@resource_name) || name.demodulize.underscore.pluralize
|
|
55
90
|
end
|
|
56
91
|
|
|
57
|
-
# Define
|
|
92
|
+
# Define la clave raíz para envolver el payload JSON (Wrapping).
|
|
93
|
+
#
|
|
94
|
+
# Por defecto utiliza `model_name.element`, lo que elimina los namespaces.
|
|
95
|
+
# Ej: `Manager::Service` -> `'service'`.
|
|
96
|
+
#
|
|
97
|
+
# @return [String] La clave paramétrica.
|
|
98
|
+
def param_key
|
|
99
|
+
resolve_config(:param_key, :@param_key) || model_name.element
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Define un middleware para el cliente HTTP/AMQP de este recurso.
|
|
58
103
|
def client_middleware(&block)
|
|
59
104
|
@client_middleware_stack ||= []
|
|
60
105
|
@client_middleware_stack << block
|
|
@@ -72,6 +117,8 @@ module BugBunny
|
|
|
72
117
|
stack
|
|
73
118
|
end
|
|
74
119
|
|
|
120
|
+
# Instancia un cliente configurado con el pool y middlewares del recurso.
|
|
121
|
+
# @return [BugBunny::Client]
|
|
75
122
|
def bug_bunny_client
|
|
76
123
|
pool = connection_pool
|
|
77
124
|
raise BugBunny::Error, "Connection pool missing for #{name}" unless pool
|
|
@@ -81,14 +128,22 @@ module BugBunny
|
|
|
81
128
|
end
|
|
82
129
|
end
|
|
83
130
|
|
|
131
|
+
# Ejecuta un bloque (o retorna un Proxy) con una configuración temporal.
|
|
132
|
+
# Útil para cambiar de exchange o routing_key para una operación específica.
|
|
133
|
+
#
|
|
134
|
+
# @example
|
|
135
|
+
# User.with(routing_key: 'urgent').create(params)
|
|
84
136
|
def with(exchange: nil, routing_key: nil, exchange_type: nil, pool: nil)
|
|
85
137
|
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" }
|
|
86
138
|
old_values = {}
|
|
87
139
|
keys.each { |k, v| old_values[k] = Thread.current[v] }
|
|
140
|
+
|
|
141
|
+
# Seteamos valores temporales
|
|
88
142
|
Thread.current[keys[:exchange]] = exchange if exchange
|
|
89
143
|
Thread.current[keys[:exchange_type]] = exchange_type if exchange_type
|
|
90
144
|
Thread.current[keys[:pool]] = pool if pool
|
|
91
145
|
Thread.current[keys[:routing_key]] = routing_key if routing_key
|
|
146
|
+
|
|
92
147
|
if block_given?
|
|
93
148
|
begin; yield; ensure; keys.each { |k, v| Thread.current[v] = old_values[k] }; end
|
|
94
149
|
else
|
|
@@ -96,36 +151,41 @@ module BugBunny
|
|
|
96
151
|
end
|
|
97
152
|
end
|
|
98
153
|
|
|
154
|
+
# Proxy para permitir encadenamiento: User.with(...).find(1)
|
|
99
155
|
class ScopeProxy < BasicObject
|
|
100
156
|
def initialize(target, keys, old_values); @target = target; @keys = keys; @old_values = old_values; end
|
|
101
157
|
def method_missing(method, *args, &block); @target.public_send(method, *args, &block); ensure; @keys.each { |k, v| ::Thread.current[v] = @old_values[k] }; end
|
|
102
158
|
end
|
|
103
159
|
|
|
104
160
|
# Calcula la Routing Key.
|
|
105
|
-
# @
|
|
161
|
+
# @return [String]
|
|
106
162
|
def calculate_routing_key(id = nil)
|
|
107
|
-
|
|
163
|
+
# 1. Contexto .with
|
|
164
|
+
manual_rk = thread_config(:routing_key)
|
|
108
165
|
return manual_rk if manual_rk
|
|
166
|
+
|
|
167
|
+
# 2. Configuración estática
|
|
109
168
|
static_rk = resolve_config(:routing_key, :@routing_key)
|
|
110
169
|
return static_rk if static_rk.present?
|
|
170
|
+
|
|
171
|
+
# 3. Default: Resource name
|
|
111
172
|
resource_name
|
|
112
173
|
end
|
|
113
174
|
|
|
114
|
-
# @!group Acciones CRUD RESTful
|
|
175
|
+
# @!group Acciones CRUD RESTful (Clase)
|
|
115
176
|
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
# @param filters [Hash] Parámetros de consulta (Query params).
|
|
177
|
+
# Busca recursos que coincidan con los filtros.
|
|
178
|
+
# Envía: GET resource?query
|
|
119
179
|
def where(filters = {})
|
|
120
180
|
rk = calculate_routing_key
|
|
121
181
|
path = resource_name
|
|
122
182
|
path += "?#{URI.encode_www_form(filters)}" if filters.present?
|
|
123
183
|
|
|
124
|
-
# REST: GET collection
|
|
125
184
|
response = bug_bunny_client.request(path, method: :get, exchange: current_exchange, exchange_type: current_exchange_type, routing_key: rk)
|
|
126
185
|
|
|
127
186
|
return [] unless response['body'].is_a?(Array)
|
|
128
187
|
response['body'].map do |attrs|
|
|
188
|
+
# Al instanciar aquí, se captura el contexto si estamos dentro de un .with
|
|
129
189
|
inst = new(attrs)
|
|
130
190
|
inst.persisted = true
|
|
131
191
|
inst.send(:clear_changes_information)
|
|
@@ -135,14 +195,12 @@ module BugBunny
|
|
|
135
195
|
|
|
136
196
|
def all; where({}); end
|
|
137
197
|
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
# @param id [String, Integer] ID del recurso.
|
|
198
|
+
# Busca un recurso por ID.
|
|
199
|
+
# Envía: GET resource/id
|
|
141
200
|
def find(id)
|
|
142
201
|
rk = calculate_routing_key(id)
|
|
143
202
|
path = "#{resource_name}/#{id}"
|
|
144
203
|
|
|
145
|
-
# REST: GET member
|
|
146
204
|
response = bug_bunny_client.request(path, method: :get, exchange: current_exchange, exchange_type: current_exchange_type, routing_key: rk)
|
|
147
205
|
|
|
148
206
|
return nil if response.nil? || response['status'] == 404
|
|
@@ -156,6 +214,7 @@ module BugBunny
|
|
|
156
214
|
instance
|
|
157
215
|
end
|
|
158
216
|
|
|
217
|
+
# Crea un nuevo recurso.
|
|
159
218
|
def create(payload)
|
|
160
219
|
instance = new(payload)
|
|
161
220
|
instance.save
|
|
@@ -165,18 +224,45 @@ module BugBunny
|
|
|
165
224
|
|
|
166
225
|
# @!group Instancia
|
|
167
226
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
227
|
+
# Inicializa una nueva instancia del recurso.
|
|
228
|
+
#
|
|
229
|
+
# **IMPORTANTE:** Captura la configuración del contexto actual (`.with`)
|
|
230
|
+
# y la guarda en la instancia. Esto permite que objetos creados dentro de un bloque `with`
|
|
231
|
+
# mantengan esa configuración (routing_key, exchange) durante todo su ciclo de vida,
|
|
232
|
+
# incluso si `save` se llama fuera del bloque.
|
|
233
|
+
#
|
|
234
|
+
# @param attributes [Hash] Atributos iniciales.
|
|
173
235
|
def initialize(attributes = {})
|
|
174
236
|
@remote_attributes = {}.with_indifferent_access
|
|
175
237
|
@persisted = false
|
|
238
|
+
|
|
239
|
+
# === CAPTURA DE CONTEXTO ===
|
|
240
|
+
@routing_key = self.class.thread_config(:routing_key)
|
|
241
|
+
@exchange = self.class.thread_config(:exchange)
|
|
242
|
+
@exchange_type = self.class.thread_config(:exchange_type)
|
|
243
|
+
|
|
176
244
|
assign_attributes(attributes)
|
|
177
245
|
super()
|
|
178
246
|
end
|
|
179
247
|
|
|
248
|
+
# Prioridad Routing Key: 1. Instancia (Capturada), 2. Clase
|
|
249
|
+
def calculate_routing_key(id=nil)
|
|
250
|
+
return @routing_key if @routing_key
|
|
251
|
+
self.class.calculate_routing_key(id)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Prioridad Exchange: 1. Instancia (Capturada), 2. Clase
|
|
255
|
+
def current_exchange
|
|
256
|
+
@exchange || self.class.current_exchange
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Prioridad Exchange Type: 1. Instancia (Capturada), 2. Clase
|
|
260
|
+
def current_exchange_type
|
|
261
|
+
@exchange_type || self.class.current_exchange_type
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def bug_bunny_client; self.class.bug_bunny_client; end
|
|
265
|
+
|
|
180
266
|
def persisted?; !!@persisted; end
|
|
181
267
|
|
|
182
268
|
def assign_attributes(new_attributes)
|
|
@@ -189,12 +275,13 @@ module BugBunny
|
|
|
189
275
|
save
|
|
190
276
|
end
|
|
191
277
|
|
|
278
|
+
# Retorna solo los atributos que han cambiado.
|
|
192
279
|
def changes_to_send
|
|
193
280
|
return changes.transform_values(&:last) unless changes.empty?
|
|
194
281
|
@remote_attributes.except('id', 'ID', 'Id', '_id')
|
|
195
282
|
end
|
|
196
283
|
|
|
197
|
-
#
|
|
284
|
+
# Métodos mágicos para atributos.
|
|
198
285
|
def method_missing(method_name, *args, &block)
|
|
199
286
|
attribute_name = method_name.to_s
|
|
200
287
|
if attribute_name.end_with?('=')
|
|
@@ -225,7 +312,13 @@ module BugBunny
|
|
|
225
312
|
|
|
226
313
|
# @!group Persistencia RESTful
|
|
227
314
|
|
|
228
|
-
# Guarda el registro
|
|
315
|
+
# Guarda el registro.
|
|
316
|
+
# Envía POST si es nuevo, PUT si ya existe.
|
|
317
|
+
#
|
|
318
|
+
# **AUTOMÁTICO:** Envuelve los parámetros en la clave del modelo (`param_key`).
|
|
319
|
+
# Ej: Manager::Service -> "service". Esto facilita `params.require(:service)`.
|
|
320
|
+
#
|
|
321
|
+
# @return [Boolean] true si se guardó correctamente.
|
|
229
322
|
def save
|
|
230
323
|
return false unless valid?
|
|
231
324
|
|
|
@@ -233,15 +326,22 @@ module BugBunny
|
|
|
233
326
|
is_new = !persisted?
|
|
234
327
|
rk = calculate_routing_key(id)
|
|
235
328
|
|
|
236
|
-
#
|
|
329
|
+
# 1. Obtenemos el payload plano (atributos modificados)
|
|
330
|
+
flat_payload = changes_to_send
|
|
331
|
+
|
|
332
|
+
# 2. Wrappeamos automáticamente en la clave del modelo
|
|
333
|
+
key = self.class.param_key
|
|
334
|
+
wrapped_payload = { key => flat_payload }
|
|
335
|
+
|
|
336
|
+
# Mapeo a verbos HTTP
|
|
237
337
|
if is_new
|
|
238
338
|
# REST: POST resource (Create)
|
|
239
339
|
path = self.class.resource_name
|
|
240
|
-
response = bug_bunny_client.request(path, method: :post, exchange: current_exchange, exchange_type: current_exchange_type, routing_key: rk, body:
|
|
340
|
+
response = bug_bunny_client.request(path, method: :post, exchange: current_exchange, exchange_type: current_exchange_type, routing_key: rk, body: wrapped_payload)
|
|
241
341
|
else
|
|
242
342
|
# REST: PUT resource/id (Update)
|
|
243
343
|
path = "#{self.class.resource_name}/#{id}"
|
|
244
|
-
response = bug_bunny_client.request(path, method: :put, exchange: current_exchange, exchange_type: current_exchange_type, routing_key: rk, body:
|
|
344
|
+
response = bug_bunny_client.request(path, method: :put, exchange: current_exchange, exchange_type: current_exchange_type, routing_key: rk, body: wrapped_payload)
|
|
245
345
|
end
|
|
246
346
|
|
|
247
347
|
handle_save_response(response)
|
|
@@ -251,12 +351,12 @@ module BugBunny
|
|
|
251
351
|
false
|
|
252
352
|
end
|
|
253
353
|
|
|
254
|
-
# Elimina el registro
|
|
354
|
+
# Elimina el registro.
|
|
355
|
+
# Envía DELETE resource/id.
|
|
255
356
|
def destroy
|
|
256
357
|
return false unless persisted?
|
|
257
358
|
|
|
258
359
|
run_callbacks(:destroy) do
|
|
259
|
-
# REST: DELETE resource/id
|
|
260
360
|
path = "#{self.class.resource_name}/#{id}"
|
|
261
361
|
rk = calculate_routing_key(id)
|
|
262
362
|
|
data/lib/bug_bunny/version.rb
CHANGED
data/lib/bug_bunny.rb
CHANGED
|
@@ -95,7 +95,7 @@ module BugBunny
|
|
|
95
95
|
username: options[:username] || default.username,
|
|
96
96
|
password: options[:password] || default.password,
|
|
97
97
|
vhost: options[:vhost] || default.vhost,
|
|
98
|
-
logger: options[:logger] || default.
|
|
98
|
+
logger: options[:logger] || default.bunny_logger,
|
|
99
99
|
automatically_recover: options[:automatically_recover] || default.automatically_recover,
|
|
100
100
|
network_recovery_interval: options[:network_recovery_interval] || default.network_recovery_interval,
|
|
101
101
|
connection_timeout: options[:connection_timeout] || default.connection_timeout,
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bug_bunny
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- gabix
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-02-
|
|
11
|
+
date: 2026-02-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bunny
|
|
@@ -100,14 +100,14 @@ dependencies:
|
|
|
100
100
|
requirements:
|
|
101
101
|
- - ">="
|
|
102
102
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '0'
|
|
103
|
+
version: '2.0'
|
|
104
104
|
type: :runtime
|
|
105
105
|
prerelease: false
|
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
107
|
requirements:
|
|
108
108
|
- - ">="
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '0'
|
|
110
|
+
version: '2.0'
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
112
|
name: ostruct
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -128,57 +128,59 @@ dependencies:
|
|
|
128
128
|
requirements:
|
|
129
129
|
- - ">="
|
|
130
130
|
- !ruby/object:Gem::Version
|
|
131
|
-
version: '0'
|
|
131
|
+
version: '2.0'
|
|
132
132
|
type: :development
|
|
133
133
|
prerelease: false
|
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
135
|
requirements:
|
|
136
136
|
- - ">="
|
|
137
137
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: '0'
|
|
138
|
+
version: '2.0'
|
|
139
139
|
- !ruby/object:Gem::Dependency
|
|
140
140
|
name: rake
|
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
|
142
142
|
requirements:
|
|
143
|
-
- - "
|
|
143
|
+
- - "~>"
|
|
144
144
|
- !ruby/object:Gem::Version
|
|
145
|
-
version: '0'
|
|
145
|
+
version: '13.0'
|
|
146
146
|
type: :development
|
|
147
147
|
prerelease: false
|
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
|
149
149
|
requirements:
|
|
150
|
-
- - "
|
|
150
|
+
- - "~>"
|
|
151
151
|
- !ruby/object:Gem::Version
|
|
152
|
-
version: '0'
|
|
152
|
+
version: '13.0'
|
|
153
153
|
- !ruby/object:Gem::Dependency
|
|
154
154
|
name: rspec
|
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
|
156
156
|
requirements:
|
|
157
|
-
- - "
|
|
157
|
+
- - "~>"
|
|
158
158
|
- !ruby/object:Gem::Version
|
|
159
|
-
version: '0'
|
|
159
|
+
version: '3.0'
|
|
160
160
|
type: :development
|
|
161
161
|
prerelease: false
|
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
|
163
163
|
requirements:
|
|
164
|
-
- - "
|
|
164
|
+
- - "~>"
|
|
165
165
|
- !ruby/object:Gem::Version
|
|
166
|
-
version: '0'
|
|
166
|
+
version: '3.0'
|
|
167
167
|
- !ruby/object:Gem::Dependency
|
|
168
168
|
name: yard
|
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
|
170
170
|
requirements:
|
|
171
|
-
- - "
|
|
171
|
+
- - "~>"
|
|
172
172
|
- !ruby/object:Gem::Version
|
|
173
|
-
version: '0'
|
|
173
|
+
version: '0.9'
|
|
174
174
|
type: :development
|
|
175
175
|
prerelease: false
|
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
|
177
177
|
requirements:
|
|
178
|
-
- - "
|
|
178
|
+
- - "~>"
|
|
179
179
|
- !ruby/object:Gem::Version
|
|
180
|
-
version: '0'
|
|
181
|
-
description:
|
|
180
|
+
version: '0.9'
|
|
181
|
+
description: BugBunny is a lightweight RPC framework for Ruby on Rails over RabbitMQ.
|
|
182
|
+
It simulates a RESTful architecture with an intelligent router, Active Record-like
|
|
183
|
+
resources, and middleware support.
|
|
182
184
|
email:
|
|
183
185
|
- gab.edera@gmail.com
|
|
184
186
|
executables: []
|