bug_bunny 1.0.1 → 2.0.0

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.
@@ -1,108 +1,308 @@
1
+ # host: Especifica la dirección de red (hostname o IP) donde se está ejecutando el servidor RabbitMQ.
2
+ # username: El nombre de usuario que se utiliza para la autenticación.
3
+ # password: La contraseña para la autenticación.
4
+ # vhost: Define el Virtual Host (VHost) al que se conectará la aplicación. Un VHost actúa como un namespace virtual dentro del broker, aislando entornos y recursos.
5
+ # logger: Indica a Bunny que use el sistema de logging estándar de Rails, integrando los mensajes del cliente AMQP con el resto de los logs de tu aplicación.
6
+ #
7
+ # Resiliencia y Recuperación Automática
8
+ #
9
+ # Estos parámetros son fundamentales para manejar fallos de red y garantizar que la aplicación se recupere sin intervención manual.
10
+ # automatically_recover: Indica al cliente Bunny que debe intentar automáticamente reestablecer la conexión y todos los recursos asociados (canales, colas, exchanges) si la conexión se pierde debido a un fallo de red o un reinicio del broker. Nota: Este parámetro puede entrar en conflicto con un bucle de retry manual).
11
+ # network_recovery_interval: El tiempo que Bunny esperará entre intentos consecutivos de reconexión de red.
12
+ # heartbeat: El intervalo de tiempo (en segundos) en el que el cliente y el servidor deben enviarse un pequeño paquete ("latido"). Si no se recibe un heartbeat durante dos intervalos consecutivos, se asume que la conexión ha muerto (generalmente por un fallo de red o un proceso colgado), lo que dispara el mecanismo de recuperación.
13
+ #
14
+ # Tiempos de Espera (Timeouts)
15
+ #
16
+ # Estos parámetros previenen que la aplicación se bloquee indefinidamente esperando una respuesta del servidor.
17
+ # connection_timeout: Tiempo máximo (en segundos) que Bunny esperará para establecer la conexión TCP inicial con el servidor RabbitMQ.
18
+ # read_timeout: Tiempo máximo (en segundos) que la conexión esperará para leer datos del socket. Si el servidor se queda en silencio por más de 30 segundos, el socket se cerrará.
19
+ # write_timeout: Tiempo máximo (en segundos) que la conexión esperará para escribir datos en el socket. Útil para manejar escenarios donde la red es lenta o está congestionada.
20
+ # continuation_timeout: Es un timeout interno de protocolo AMQP (dado en milisegundos). Define cuánto tiempo esperará el cliente para que el servidor responda a una operación que requiere múltiples frames o pasos (como una transacción o una confirmación compleja). En este caso, son 15 segundos.
1
21
  module BugBunny
2
22
  class Rabbit
3
- require 'bunny'
4
-
5
- attr_accessor :exchange,
6
- :rabbit_channel,
7
- :confirm_select,
8
- :no_ack,
9
- :persistent,
10
- :block,
11
- :logger,
12
- :identifier,
13
- :connection
14
-
15
- def initialize(attrs = {})
16
- @block = attrs[:block] || true
17
- @no_ack = attrs[:no_ack] || true
18
- @persistent = attrs[:persistent] || true
19
- @confirm_select = attrs[:confirm_select] || true
20
- @logger = attrs[:logger] || Logger.new('./log/bug_rabbit.log', 'monthly')
21
- @identifier = SecureRandom.uuid
22
-
23
- create_connection
24
- set_channel
23
+ include ActiveModel::Model
24
+ include ActiveModel::Attributes
25
+
26
+ DEFAULT_EXCHANGE_OPTIONS = { durable: false, auto_delete: false }.freeze
27
+
28
+ # DEFAULT_MAX_PRIORITY = 10
29
+
30
+ DEFAULT_QUEUE_OPTIONS = {
31
+ exclusive: false,
32
+ durable: false,
33
+ auto_delete: true,
34
+ # arguments: { 'x-max-priority' => DEFAULT_MAX_PRIORITY }
35
+ }.freeze
36
+
37
+ attr_accessor :connection, :queue, :exchange
38
+
39
+ def channel
40
+ @channel_mutex ||= Mutex.new
41
+ return @channel if @channel&.open?
42
+
43
+ @channel_mutex.synchronize do
44
+ return @channel if @channel&.open?
45
+
46
+ @channel = connection.create_channel
47
+ @channel.confirm_select
48
+ @channel.prefetch(RABBIT_CHANNEL_PREFETCH) # Limita mensajes concurrentes por consumidor
49
+ @channel
50
+ end
51
+ end
52
+
53
+ def build_exchange(name: nil, type: 'direct', opts: {})
54
+ return @exchange if defined?(@exchange)
55
+
56
+ exchange_options = DEFAULT_EXCHANGE_OPTIONS.merge(opts.compact)
57
+
58
+ if name.blank?
59
+ @exchange = channel.default_exchange
60
+ return @exchange
61
+ end
62
+
63
+ Rails.logger.info("ExchangeName: #{name}, ExchangeType: #{type}, opts: #{opts}")
64
+
65
+ @exchange = case type.to_sym
66
+ when :topic
67
+ channel.topic(name, exchange_options)
68
+ when :direct
69
+ channel.direct(name, exchange_options)
70
+ when :fanout
71
+ channel.fanout(name, exchange_options)
72
+ when :headers
73
+ channel.headers(name, exchange_options)
74
+ end
25
75
  end
26
76
 
27
- def set_channel
28
- logger.debug("Set Channel: #{connection.status}") if logger
29
- try(:close_channel)
30
- @rabbit_channel = connection.create_channel
31
- @exchange = channel.default_exchange
32
- channel.confirm_select if confirm_select
33
- @rabbit_channel
77
+ def default_publish_options
78
+ @default_publish_opts ||= {
79
+ persistent: false,
80
+ app_id: Rails.application.class.module_parent_name
81
+ }.freeze
82
+
83
+ # Solo generamos valores dinámicos por llamada
84
+ @default_publish_opts.merge(
85
+ timestamp: Time.current.to_i,
86
+ correlation_id: SecureRandom.uuid
87
+ )
34
88
  end
35
89
 
36
- def channel
37
- open? ? @rabbit_channel : set_channel
90
+ def build_queue(name: '', opts: {})
91
+ name = name.to_s
92
+ queue_options = DEFAULT_QUEUE_OPTIONS.merge(opts.compact)
93
+ Rails.logger.info("QueueName: #{name}, opts: #{queue_options}")
94
+ @queue = channel.queue(name, queue_options)
38
95
  end
39
96
 
40
- def close
41
- @rabbit_channel.close if defined?(@rabbit_channel)
42
- connection.close if connection.present?
43
- rescue Bunny::ChannelAlreadyClosed
44
- nil
97
+ def publish!(msg, opts)
98
+ options = default_publish_options.merge(opts.compact)
99
+
100
+ msg = msg.instance_of?(Hash) ? msg.to_json : msg.to_s
101
+
102
+ Rails.logger.info("Message: #{msg}")
103
+ Rails.logger.info("Options: #{options}")
104
+
105
+ exchange.publish(msg, options)
106
+ # channel.wait_for_confirms # Esto solo confirma que el mensaje llego el exchange
107
+ rescue Bunny::Exception => e
108
+ Rails.logger.error(e)
109
+ raise BugBunny::PublishError, e
45
110
  end
46
111
 
47
- def close_channel
48
- @rabbit_channel.close if defined?(@rabbit_channel)
112
+ def publish_and_consume!(msg, opts)
113
+ options = default_publish_options.merge(opts.compact)
114
+
115
+ response_latch = Concurrent::CountDownLatch.new(1)
116
+ response = nil
117
+
118
+ reply_queue = channel.queue('', exclusive: true, durable: false, auto_delete: true)
119
+ options[:reply_to] = reply_queue.name
120
+
121
+ subscription = reply_queue.subscribe(manual_ack: true, block: false) do |delivery_info, properties, body|
122
+ Rails.logger.debug("CONSUMER DeliveryInfo: #{delivery_info}")
123
+ Rails.logger.debug("CONSUMER Properties: #{properties}")
124
+ Rails.logger.debug("CONSUMER Body: #{body}")
125
+ if properties.correlation_id == options[:correlation_id]
126
+ response = ActiveSupport::JSON.decode(body).deep_symbolize_keys.with_indifferent_access
127
+ channel.ack(delivery_info.delivery_tag)
128
+ response_latch.count_down
129
+ else
130
+ Rails.logger.debug('Correlation_id not match')
131
+ # Si el correlation_id no coincide, rechazamos el mensaje para que RabbitMQ lo maneje
132
+ channel.reject(delivery_info.delivery_tag, false)
133
+ end
134
+ end
135
+
136
+ Rails.logger.debug("PUBLISHER Message: #{msg}")
137
+ Rails.logger.debug("PUBLISHER Options: #{options}")
138
+ publish!(msg, options)
139
+
140
+ if response_latch.wait(RABBIT_READ_TIMEOUT)
141
+ subscription.cancel
142
+ build_response(status: response[:status], body: response[:body])
143
+ else
144
+ raise "Timeout: No response received within #{RABBIT_READ_TIMEOUT} seconds."
145
+ end
146
+ rescue BugBunny::ResponseError::Base => e
147
+ subscription&.cancel
148
+ raise e
149
+ rescue RuntimeError => e
150
+ subscription&.cancel
151
+ Rails.logger.error("[Rabbit] Error in publish_and_consume: #{e.class} - <#{e.message}>")
152
+ raise(BugBunny::ResponseError::RequestTimeout, e.message) if e.message.include?('Timeout')
153
+
154
+ raise BugBunny::ResponseError::InternalServerError, e.message
155
+ rescue StandardError => e
156
+ subscription&.cancel
157
+ Rails.logger.error("[Rabbit] Error in publish_and_consume: #{e.class} - <#{e.message}>")
158
+ raise e
49
159
  end
50
160
 
51
- def status
52
- {
53
- connection: connection.status,
54
- channel: @rabbit_channel.status,
55
- identifier: identifier
56
- }
161
+ def parse_route(route)
162
+ # De momento no resuelve anidado
163
+ segments = route.split('/')
164
+ controller_name = segments[0]
165
+ action_name = 'index'
166
+ id = nil
167
+
168
+ case segments.length
169
+ when 2
170
+ # Patrón: controller/action (Ej: 'secrets/index', 'swarm/info')
171
+ action_name = segments[1]
172
+ when 3
173
+ # Patrón: controller/id/action (Ej: 'secrets/123/update', 'services/999/destroy')
174
+ id = segments[1]
175
+ action_name = segments[2]
176
+ end
177
+
178
+ { controller: controller_name, action: action_name, id: id }
57
179
  end
58
180
 
59
- def open?
60
- (connection.status == :open) &&
61
- (@rabbit_channel.status == :open)
181
+ def consume!
182
+ queue.subscribe(manual_ack: true, block: true) do |delivery_info, properties, body|
183
+ Rails.logger.debug("DeliveryInfo: #{delivery_info}")
184
+ Rails.logger.debug("Properties: #{properties}")
185
+ Rails.logger.debug("Body: #{body}")
186
+
187
+ raise StandardError, 'Undefined properties.type' if properties.type.blank?
188
+
189
+ route = parse_route(properties.type)
190
+
191
+ headers = {
192
+ type: properties.type,
193
+ controller: route[:controller],
194
+ action: route[:action],
195
+ id: route[:id],
196
+ content_type: properties.content_type,
197
+ content_encoding: properties.content_encoding,
198
+ correlation_id: properties.correlation_id
199
+ }
200
+
201
+ controller = "rabbit/controllers/#{route[:controller]}".camelize.constantize
202
+ response_payload = controller.call(headers: headers, body: body)
203
+
204
+ Rails.logger.debug("Response: #{response_payload}")
205
+
206
+ if properties.reply_to.present?
207
+ Rails.logger.info("Sending response to #{properties.reply_to}")
208
+
209
+ # Publicar la respuesta directamente a la cola de respuesta
210
+ # No se necesita un exchange, se publica a la cola por su nombre
211
+ channel.default_exchange.publish(
212
+ response_payload.to_json,
213
+ routing_key: properties.reply_to,
214
+ correlation_id: properties.correlation_id
215
+ )
216
+ end
217
+
218
+ channel.ack(delivery_info.delivery_tag)
219
+ rescue NoMethodError => e # action controller no exist
220
+ Rails.logger.error(e)
221
+ channel.reject(delivery_info.delivery_tag, false)
222
+ rescue NameError => e # Controller no exist
223
+ Rails.logger.error(e)
224
+ channel.reject(delivery_info.delivery_tag, false)
225
+ rescue StandardError => e
226
+ Rails.logger.error("Error processing message: #{e.message} (#{e.class})")
227
+ # Reject the message and do NOT re-queue it immediately.
228
+ channel.reject(delivery_info.delivery_tag, false)
229
+ end
62
230
  end
63
231
 
64
- def connection_openned?
65
- [:open, :connecting, :connected].include?(connection.status)
232
+ # El success y el error es para tener compatibilidad con el bug_bunny
233
+ def build_response(status:, body:)
234
+ case status
235
+ when 'success' then body # Old compatibility
236
+ when 'error' then raise BugBunny::ResponseError::InternalServerError, body # Old compatibility
237
+ when 200, 201 then body
238
+ when 204 then nil
239
+ when 400 then raise BugBunny::ResponseError::BadRequest, body.to_json
240
+ when 404 then raise BugBunny::ResponseError::NotFound
241
+ when 406 then raise BugBunny::ResponseError::NotAcceptable
242
+ when 422 then raise BugBunny::ResponseError::UnprocessableEntity, body.to_json
243
+ when 500 then raise BugBunny::ResponseError::InternalServerError, body.to_json
244
+ else
245
+ raise BugBunny::ResponseError::Base, body.to_json
246
+ end
66
247
  end
67
248
 
68
- # status = :open, :connected, :connecting,
69
- # :closing, :disconnected, :not_connected, :closed
70
- def create_connection
71
- # if WisproUtils::Config.defaults.use_tls
72
- # path = (Rails.root.join('private', 'certs') rescue './private/certs')
73
- # options.merge!(tls: true,
74
- # port: ENV['RABBIT_SSL_PORT'] || 5671,
75
- # log_level: ENV['LOG_LEVEL'] || :debug,
76
- # verify_peer: true,
77
- # tls_cert: "#{path}/cert.pem",
78
- # tls_key: "#{path}/key.pem",
79
- # tls_ca_certificates: ["#{path}/ca.pem"])
80
- # end
81
- options = {}
82
-
83
- raise "Need user" if BugBunny.configuration.user.blank?
84
- raise "Need pass" if BugBunny.configuration.pass.blank?
85
- raise "Need host" if BugBunny.configuration.host.blank?
86
-
87
- bunny_logger = BugBunny.configuration.logger || ::Logger.new('./log/bunny.log', 7, 10485760)
88
- bunny_logger.level = BugBunny.configuration.log_level || ::Logger::INFO
89
-
90
- options.merge!(
91
- heartbeat_interval: 20, # 20.seconds per connection
92
- logger: bunny_logger,
93
- # Override bunny client_propierties
94
- client_properties: { product: identifier, platform: '' }
95
- )
249
+ def self.run_consumer(connection:, exchange:, exchange_type:, queue_name:, routing_key:, queue_opts: {})
250
+ app = new(connection: connection)
251
+ app.build_exchange(name: exchange, type: exchange_type)
252
+ app.build_queue(name: queue_name, opts: queue_opts)
253
+ app.queue.bind(app.exchange, routing_key: routing_key)
254
+ app.consume!
255
+ health_check_thread = start_health_check(app, queue_name: queue_name, exchange_name: exchange, exchange_type: exchange_type)
256
+ health_check_thread.wait_for_termination
257
+ raise 'Health check error: Forcing reconnect.'
258
+ rescue StandardError => e
259
+ # Esto lo pongo por que si levanto el rabbit y el consumer a la vez
260
+ # El rabbit esta una banda de tiempo hasta aceptar conexiones, por lo que
261
+ # el consumer explota 2 millones de veces, por lo tanto con esto hago
262
+ # la espera ocupada y me evito de ponerlo en el entrypoint-docker
263
+ Rails.logger.error("[RABBIT] Consumer error: #{e.message} (#{e.class})")
264
+ Rails.logger.debug("[RABBIT] Consumer error: #{e.backtrace}")
265
+ connection&.close
266
+ sleep RABBIT_NETWORK_RECOVERY
267
+ retry
268
+ end
96
269
 
97
- logger&.debug('Stablish new connection to rabbit')
98
- logger&.debug(BugBunny.configuration.url)
270
+ def self.start_health_check(app, queue_name:, exchange_name:, exchange_type:)
271
+ task = Concurrent::TimerTask.new(execution_interval: RABBIT_HEALT_CHECK) do
272
+ # con esto veo si el exachange o la cola no la borraron desde la vista de rabbit
273
+ app.channel.exchange_declare(exchange_name, exchange_type, passive: true)
274
+ app.channel.queue_declare(queue_name, passive: true)
275
+ rescue Bunny::NotFound
276
+ Rails.logger.error("Health check failed: Queue '#{queue_name}' no longer exists!")
277
+ app.connection.close
278
+ task.shutdown # Detenemos la tarea para que no se ejecute de nuevo
279
+ rescue StandardError => e
280
+ Rails.logger.error("Health check error: #{e.message}. Forcing reconnect.")
281
+ app.connection.close
282
+ task.shutdown
283
+ end
99
284
 
100
- rabbit_conn = Bunny.new(BugBunny.configuration.url, options)
101
- rabbit_conn.start
285
+ task.execute
286
+ task
287
+ end
102
288
 
103
- logger&.debug("New status connection: #{rabbit_conn.status}")
289
+ def self.create_connection(host: nil, username: nil, password: nil, vhost: nil)
290
+ bunny = Bunny.new(
291
+ host: host || BugBunny.configuration.host,
292
+ username: username || BugBunny.configuration.username,
293
+ password: password || BugBunny.configuration.password,
294
+ vhost: vhost || BugBunny.configuration.vhost,
295
+ logger: BugBunny.configuration.logger || Rails.logger,
296
+ automatically_recover: BugBunny.configuration.automatically_recover || false,
297
+ network_recovery_interval: BugBunny.configuration.network_recovery_interval || 5,
298
+ connection_timeout: BugBunny.configuration.connection_timeout || 10,
299
+ read_timeout: BugBunny.configuration.read_timeout || 90,
300
+ write_timeout: BugBunny.configuration.write_timeout || 90,
301
+ heartbeat: BugBunny.configuration.heartbeat || 30,
302
+ continuation_timeout: BugBunny.configuration.continuation_timeout || 15_000
303
+ )
104
304
 
105
- self.connection = rabbit_conn
305
+ bunny.tap(&:start)
106
306
  end
107
307
  end
108
308
  end
@@ -0,0 +1,226 @@
1
+ module BugBunny
2
+ class Resource
3
+ include ActiveModel::API
4
+ include ActiveModel::Attributes
5
+ include ActiveModel::Dirty
6
+ include ActiveModel::Validations
7
+
8
+ class << self
9
+ attr_accessor :resource_path
10
+ attr_writer :resource_name
11
+
12
+ def resource_name
13
+ @resource_name ||= name.demodulize.underscore
14
+ end
15
+
16
+ def inherited(subclass)
17
+ super
18
+
19
+ subclass.resource_path = resource_path if resource_path.present?
20
+ end
21
+ end
22
+
23
+ class ExchangeScope
24
+ attr_reader :exchange_name, :klass
25
+
26
+ def initialize(klass, exchange_name)
27
+ @klass = klass
28
+ @exchange_name = exchange_name
29
+ end
30
+
31
+ def method_missing(method_name, *args, **kwargs, &block)
32
+ if @klass.respond_to?(method_name, true)
33
+ kwargs[:exchange] = @exchange_name
34
+ @klass.execute(method_name.to_sym, *args, **kwargs, &block)
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ def respond_to_missing?(method_name, include_private = false)
41
+ @klass.respond_to?(method_name, true) || super
42
+ end
43
+ end
44
+
45
+ attribute :persisted, :boolean, default: false
46
+
47
+ def initialize(attributes = {})
48
+ attributes.each do |key, value|
49
+ attribute_name = key.to_sym
50
+ type = guess_type(value)
51
+
52
+ next if self.class.attribute_names.include?(attribute_name.to_s)
53
+
54
+ self.class.attribute attribute_name, type if type.present?
55
+ end
56
+
57
+ super(attributes)
58
+
59
+ @previously_persisted = persisted
60
+ clear_changes_information if persisted?
61
+ end
62
+
63
+ def assign_attributes(attrs)
64
+ return if attrs.blank?
65
+
66
+ attrs.each do |key, val|
67
+ setter_method = "#{key.to_s.underscore}="
68
+
69
+ next unless respond_to?(setter_method)
70
+
71
+ send(setter_method, val)
72
+ end
73
+ end
74
+
75
+ def guess_type(value)
76
+ case value
77
+ when Integer then :integer
78
+ when Float then :float
79
+ when Date then :date
80
+ when Time, DateTime then :datetime
81
+ when TrueClass, FalseClass then :boolean
82
+ when String then :string
83
+ end
84
+ end
85
+
86
+ def persisted?
87
+ persisted
88
+ end
89
+
90
+ def update(attrs_changes)
91
+ assign_attributes(attrs_changes)
92
+ save
93
+ end
94
+
95
+ def changes_to_send
96
+ attrs = {}
97
+ changes.each { |attribute, values| attrs[attribute] = values[1] }
98
+ attrs
99
+ end
100
+
101
+ def save
102
+ action = persisted? ? self.class.update_action.to_sym : self.class.create_action.to_sym
103
+
104
+ return self if persisted? && changes.empty?
105
+
106
+ obj = self.class.publisher.send(action, exchange: current_exchange, message: changes_to_send)
107
+
108
+ assign_attributes(obj) # refresco el objeto
109
+ self.persisted = true
110
+ @previously_persisted = true
111
+ clear_changes_information
112
+ true
113
+ rescue BugBunny::ResponseError::UnprocessableEntity => e
114
+ load_remote_rabbit_errors(e.message)
115
+ false
116
+ end
117
+
118
+ def destroy
119
+ return self unless persisted?
120
+
121
+ # Llamada al PUBLISHER sin el argumento 'box'
122
+ self.class.publisher.send(destroy_action.to_sym, exchange: current_exchange, id: id)
123
+
124
+ self.persisted = false
125
+ true
126
+ rescue BugBunny::ResponseError::UnprocessableEntity => e
127
+ load_remote_rabbit_errors(e.message)
128
+ false
129
+ end
130
+
131
+ def current_exchange
132
+ self.class.current_exchange
133
+ end
134
+
135
+ def self.for_exchange(exchange_name)
136
+ raise ArgumentError, 'Exchange name must be specified.' if exchange_name.blank?
137
+
138
+ ExchangeScope.new(self, exchange_name)
139
+ end
140
+
141
+ def self.execute(name, *args, **kwargs, &block)
142
+ original_exchange = Thread.current[:bugbunny_current_exchange]
143
+ Thread.current[:bugbunny_current_exchange] = kwargs[:exchange]
144
+ begin
145
+ kwargs.delete(:exchange)
146
+ send(name, *args, **kwargs, &block)
147
+ ensure
148
+ Thread.current[:bugbunny_current_exchange] = original_exchange
149
+ end
150
+ end
151
+
152
+ def self.current_exchange
153
+ Thread.current[:bugbunny_current_exchange]
154
+ end
155
+
156
+ def self.all
157
+ where
158
+ end
159
+
160
+ def self.where(query = {})
161
+ body = publisher.send(index_action.to_sym, exchange: current_exchange, message: query)
162
+ instances = []
163
+
164
+ body.each do |obj|
165
+ instance = new
166
+ instance.assign_attributes(obj.merge(persisted: true))
167
+ instances << instance
168
+ end
169
+
170
+ instances
171
+ end
172
+
173
+ def self.find(id)
174
+ obj = publisher.send(show_action.to_sym, exchange: current_exchange, id: id)
175
+ return if obj.blank?
176
+
177
+ instance = new
178
+ instance.assign_attributes(obj.merge(persisted: true))
179
+ instance
180
+ end
181
+
182
+ def self.create(payload)
183
+ instance = new(payload)
184
+ instance.save
185
+ instance
186
+ end
187
+
188
+ def self.index_action
189
+ :index
190
+ end
191
+
192
+ def self.show_action
193
+ :show
194
+ end
195
+
196
+ def self.create_action
197
+ :create
198
+ end
199
+
200
+ def self.update_action
201
+ :update
202
+ end
203
+
204
+ def self.destroy_action
205
+ :destroy
206
+ end
207
+
208
+ def self.publisher
209
+ @publisher ||= if resource_path.end_with?('/')
210
+ [resource_path, resource_name].join('').camelize.constantize
211
+ else
212
+ [resource_path, resource_name].join('/').camelize.constantize
213
+ end
214
+ end
215
+
216
+ def load_remote_rabbit_errors(remote_errors)
217
+ JSON.parse(remote_errors).each do |attribute, errors|
218
+ errors.each do |error|
219
+ self.errors.add(attribute, error['error'], **error.except('error').symbolize_keys)
220
+ end
221
+ end
222
+ end
223
+
224
+ private_class_method :all, :where, :find, :create
225
+ end
226
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BugBunny
4
- VERSION = "1.0.1"
4
+ VERSION = "2.0.0"
5
5
  end
data/lib/bug_bunny.rb CHANGED
@@ -1,22 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bunny'
4
- require_relative "bug_bunny/version"
5
- require_relative "bug_bunny/config"
6
- require_relative "bug_bunny/adapter"
7
- require_relative "bug_bunny/controller"
8
- require_relative "bug_bunny/exception"
9
- require_relative "bug_bunny/message"
10
- require_relative "bug_bunny/queue"
11
- require_relative "bug_bunny/rabbit"
12
- require_relative "bug_bunny/response"
13
- require_relative "bug_bunny/security"
14
- require_relative "bug_bunny/helpers"
15
-
16
- if defined? ::Rails::Railtie
17
- ## Rails only files
18
- require 'bug_bunny/railtie'
19
- end
4
+ require_relative 'bug_bunny/version'
5
+ require_relative 'bug_bunny/config'
6
+ require_relative 'bug_bunny/controller'
7
+ require_relative 'bug_bunny/publisher'
8
+ require_relative 'bug_bunny/exception'
9
+ require_relative 'bug_bunny/rabbit'
10
+ require_relative 'bug_bunny/resource'
20
11
 
21
12
  module BugBunny
22
13
  class << self