action_webhook 0.1.1 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54d0eddc17626872f1d795b8c25c4fda06719a1bfed4b185093c8803d442d2ad
4
- data.tar.gz: ec2b67113640e9b1d7e4ddb397858a4d38724d01024f781b492906ed629dfc62
3
+ metadata.gz: 7077d23d4f22c68b2a33a9cc9ee6e6ad911d9c9ee23a2a861bb93837c69910e7
4
+ data.tar.gz: 124749c8b109502308684d10e5a05791f99f35f26a0d01bdc1b3d11fd3ff43d3
5
5
  SHA512:
6
- metadata.gz: 5635f2e1541ecf586c52a37df1fb03b6e11e71585255cdbef8a01be30323e1d7a53f7f0ab053ff98dd7a8f3bc6e4e5d95dfbd1b0c1b86b3238577aa13bde0459
7
- data.tar.gz: e97e0940c7bf16f20d4c6457d115a2054494af9c17fd9957c3f5537774a45374ee1c395a228de33689edac80ba9f3e3b367e82baa2a972b178b3ac9df5ac719e
6
+ metadata.gz: c3be33f1a7a4528062882ea98e05caa9d61ef413bc72e4fba03788a7862e52b891f91897389e513f712fddaab7f7b5b68f4367b091f148df897a0548b25db556
7
+ data.tar.gz: 41d39d74c5ce2854375b1397552b687d64d9f3ad991eade8b92627b2f5efed8744de3fa092fe72926a244e50244a4363174b1ba91ad537bc0f485c94552d8a5c
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module ActionWebhook
4
2
  # Base class for defining and delivering webhooks
5
3
  #
@@ -24,11 +22,33 @@ module ActionWebhook
24
22
  # # Send immediately
25
23
  # UserWebhook.created(user).deliver_now
26
24
  #
27
- # # Send in background
25
+ # # Send in background (uses default queue)
28
26
  # UserWebhook.created(user).deliver_later
29
27
  #
28
+ # # Send in background with specific queue
29
+ # UserWebhook.created(user).deliver_later(queue: 'webhooks')
30
+ #
31
+ # # Send in background with delay
32
+ # UserWebhook.created(user).deliver_later(wait: 5.minutes)
33
+ #
34
+ # # Send in background with specific queue and delay
35
+ # UserWebhook.created(user).deliver_later(queue: 'webhooks', wait: 10.minutes)
36
+ #
37
+ # You can also configure the default queue at the class level:
38
+ #
39
+ # class UserWebhook < ActionWebhook::Base
40
+ # self.deliver_later_queue_name = 'webhooks'
41
+ #
42
+ # def created(user)
43
+ # @user = user
44
+ # endpoints = WebhookSubscription.where(event: 'user.created').map do |sub|
45
+ # { url: sub.url, headers: { 'Authorization' => "Bearer #{sub.token}" } }
46
+ # end
47
+ # deliver(endpoints)
48
+ # end
49
+ # end
50
+ #
30
51
  class Base
31
- # Add these lines near the top of your class
32
52
  include GlobalID::Identification if defined?(GlobalID)
33
53
  include ActiveJob::SerializationAdapter::ObjectSerializer if defined?(ActiveJob::SerializationAdapter)
34
54
 
@@ -42,249 +62,92 @@ module ActionWebhook
42
62
  # Retry configuration
43
63
  class_attribute :max_retries, instance_writer: false, default: 3
44
64
  class_attribute :retry_delay, instance_writer: false, default: 30.seconds
45
- class_attribute :retry_backoff, instance_writer: false, default: :exponential # :linear or :exponential
65
+ class_attribute :retry_backoff, instance_writer: false, default: :exponential
46
66
  class_attribute :retry_jitter, instance_writer: false, default: 5.seconds
47
67
 
48
68
  # Callbacks
49
69
  class_attribute :after_deliver_callback, instance_writer: false
50
70
  class_attribute :after_retries_exhausted_callback, instance_writer: false
51
71
 
52
- # The webhook action that will be performed
53
- attr_accessor :action_name
54
-
55
- # The webhook details (URLs and headers) that will be used
56
- attr_accessor :webhook_details
57
-
58
- # Stores the execution params for later delivery
59
- attr_accessor :params
60
-
61
- # Current attempt number for retries
62
- attr_accessor :attempts
72
+ attr_accessor :action_name, :webhook_details, :params, :attempts
63
73
 
64
- # Creates a new webhook and initializes its settings
65
74
  def initialize
66
75
  @_webhook_message = {}
67
76
  @_webhook_defaults = {}
68
77
  @attempts = 0
69
78
  end
70
79
 
71
- # Synchronously delivers the webhook
72
80
  def deliver_now
73
81
  @attempts += 1
74
82
  response = process_webhook
75
83
 
76
- # Call success callback if defined
77
84
  if response.all? { |r| r[:success] }
78
85
  invoke_callback(self.class.after_deliver_callback, response)
79
86
  elsif @attempts < self.class.max_retries
80
- # Schedule a retry with backoff
81
87
  retry_with_backoff
82
88
  else
83
- # We've exhausted all retries
84
89
  invoke_callback(self.class.after_retries_exhausted_callback, response)
85
90
  end
86
91
 
87
92
  response
88
93
  end
89
94
 
90
- # Helper method to invoke a callback that might be a symbol or a proc
91
- def invoke_callback(callback, response)
92
- return unless callback
93
-
94
- case callback
95
- when Symbol
96
- send(callback, response)
97
- when Proc
98
- callback.call(self, response)
99
- end
100
- end
101
-
102
- # Enqueues the webhook delivery via ActiveJob
103
95
  def deliver_later(options = {})
104
96
  enqueue_delivery(:deliver_now, options)
105
97
  end
106
98
 
107
- # Prepares the webhook for delivery using the current method as template name
108
- # and instance variables as template data
109
- #
110
- # @param webhook_details [Array<Hash>] Array of hashes with :url and :headers keys
111
- # @param params [Hash] Optional parameters to store for later delivery
112
- # @return [DeliveryMessenger] A messenger object for further delivery options
113
99
  def deliver(webhook_details, params = {})
114
- # Determine action name from the caller
115
100
  @action_name = caller_locations(1, 1)[0].label.to_sym
116
101
  @webhook_details = webhook_details
117
102
  @params = params
118
103
 
119
- # Return self for chaining with delivery methods
120
104
  DeliveryMessenger.new(self)
121
105
  end
122
106
 
123
- # Renders a template based on the action name
124
- #
125
- # @param variables [Hash] Optional variables to add to template context
126
- # @return [Hash] The JSON payload
127
107
  def build_payload(variables = {})
128
- # Combine instance variables and passed variables
129
- assigns = {}
130
-
131
- # Extract instance variables
132
- instance_variables.each do |ivar|
133
- # Skip internal instance variables
134
- next if ivar.to_s.start_with?("@_")
135
- next if %i[@action_name @webhook_details @params @attempts].include?(ivar)
136
-
137
- # Add to assigns with symbol key (without @)
138
- assigns[ivar.to_s[1..].to_sym] = instance_variable_get(ivar)
139
- end
140
-
141
- # Add passed variables
108
+ assigns = extract_instance_variables
142
109
  assigns.merge!(variables)
143
-
144
- # Render the template
145
110
  generate_json_from_template(@action_name, assigns)
146
111
  end
147
112
 
148
- # Posts payload(s) to the given webhook endpoints
149
- #
150
- # @param webhook_details [Array<Hash>] An array of hashes containing `url` and `headers`
151
- # @param payloads [Array<Hash>] One payload for each webhook
152
- # @return [Array<Hash>] Array of response objects with status and body
153
- def post_webhook(webhook_details, payloads)
113
+ def post_webhook(webhook_details, payload)
154
114
  responses = []
155
115
 
156
- webhook_details.each_with_index do |detail, idx|
157
- # Ensure headers exists
116
+ webhook_details.each do |detail|
158
117
  detail[:headers] ||= {}
118
+ headers = build_headers(detail[:headers])
159
119
 
160
- # Merge default headers
161
- headers = default_headers.merge(detail[:headers])
162
-
163
- # Add content type if not present
164
- headers["Content-Type"] = "application/json" unless headers.key?("Content-Type")
165
-
166
- # Add attempt tracking in headers
167
- headers["X-Webhook-Attempt"] = @attempts.to_s if @attempts.positive?
168
-
169
- response = HTTParty.post(
170
- detail[:url],
171
- body: payloads[idx].to_json,
172
- headers: headers,
173
- timeout: 10 # Add reasonable timeout
174
- )
175
-
176
- responses << {
177
- success: response.success?,
178
- status: response.code,
179
- body: response.body,
180
- url: detail[:url],
181
- attempt: @attempts
182
- }
120
+ response = send_webhook_request(detail[:url], payload, headers)
121
+ responses << build_response_hash(response, detail[:url])
122
+ log_webhook_result(response, detail[:url])
183
123
  rescue StandardError => e
184
- responses << {
185
- success: false,
186
- error: e.message,
187
- url: detail[:url],
188
- attempt: @attempts
189
- }
190
- logger.error("Webhook delivery failed: #{e.message} for URL: #{detail[:url]} (Attempt #{@attempts})")
124
+ responses << build_error_response_hash(e, detail[:url])
125
+ log_webhook_error(e, detail[:url])
191
126
  end
192
127
 
193
128
  responses
194
129
  end
195
130
 
196
- # Renders a JSON payload from a `.json.erb` template
197
- #
198
- # @param event_name [Symbol] the name of the webhook method (e.g. `:created`)
199
- # @param assigns [Hash] local variables to pass into the template
200
- # @return [Hash] the parsed JSON payload
201
131
  def generate_json_from_template(input_event_name, assigns = {})
202
132
  event_name = extract_method_name(input_event_name.to_s)
203
- webhook_class_name = self.class.name.underscore
204
-
205
- # Possible template locations
206
- possible_paths = [
207
- # Main app templates
208
- File.join(Rails.root.to_s, "app/webhooks/#{webhook_class_name}/#{event_name}.json.erb"),
209
-
210
- # Engine templates
211
- engine_root && File.join(engine_root, "app/webhooks/#{webhook_class_name}/#{event_name}.json.erb"),
212
-
213
- # Namespaced templates in engine
214
- engine_root && self.class.module_parent != Object &&
215
- File.join(engine_root,
216
- "app/webhooks/#{self.class.module_parent.name.underscore}/#{webhook_class_name.split("/").last}/#{event_name}.json.erb")
217
- ].compact
133
+ template_path = find_template_path(event_name)
218
134
 
219
- # Find the first template that exists
220
- template_path = possible_paths.find { |path| File.exist?(path) }
135
+ raise ArgumentError, "Template not found for #{event_name}" unless template_path
221
136
 
222
- unless template_path
223
- raise ArgumentError, "Template not found for #{event_name} in paths:\n#{possible_paths.join("\n")}"
224
- end
225
-
226
- template = ERB.new(File.read(template_path))
227
- json = template.result_with_hash(assigns)
228
- JSON.parse(json)
137
+ render_json_template(template_path, assigns)
229
138
  rescue JSON::ParserError => e
230
- raise "Invalid JSON in template #{event_name}: #{e.message}"
139
+ raise ArgumentError, "Invalid JSON in template #{event_name}: #{e.message}"
231
140
  end
232
141
 
233
142
  def extract_method_name(path)
234
143
  path.include?("#") ? path.split("#").last : path
235
144
  end
236
145
 
237
- # Process the webhook to generate and send the payload
238
146
  def process_webhook
239
- # Render the message
240
- payloads = [build_payload]
241
-
242
- # Post the webhook
243
- post_webhook(webhook_details, payloads)
244
- end
245
-
246
- # Schedule a retry with appropriate backoff delay
247
- # Modify the retry_with_backoff method:
248
- def retry_with_backoff
249
- delay = calculate_backoff_delay
250
-
251
- logger.info("Scheduling webhook retry #{@attempts + 1}/#{self.class.max_retries} in #{delay} seconds")
252
-
253
- # Get the actual job class by evaluating the proc
254
- job_class = self.class.delivery_job.is_a?(Proc) ? self.class.delivery_job.call : self.class.delivery_job
255
-
256
- # Serialize the webhook and pass the serialized data instead of the object
257
- serialized_webhook = serialize
258
-
259
- # Re-enqueue with the calculated delay
260
- if deliver_later_queue_name
261
- job_class.set(queue: deliver_later_queue_name, wait: delay).perform_later("deliver_now", serialized_webhook)
262
- else
263
- job_class.set(wait: delay).perform_later("deliver_now", serialized_webhook)
264
- end
147
+ payload = build_payload
148
+ post_webhook(webhook_details, payload)
265
149
  end
266
150
 
267
- # Calculate delay based on retry strategy
268
- def calculate_backoff_delay
269
- base_delay = self.class.retry_delay
270
-
271
- delay = case self.class.retry_backoff
272
- when :exponential
273
- # 30s, 60s, 120s, etc.
274
- base_delay * (2**(@attempts - 1))
275
- when :linear
276
- # 30s, 60s, 90s, etc.
277
- base_delay * @attempts
278
- else
279
- base_delay
280
- end
281
-
282
- # Add jitter to prevent thundering herd problem
283
- jitter = rand(self.class.retry_jitter)
284
- delay + jitter
285
- end
286
-
287
- # For ActiveJob serialization
288
151
  def serialize
289
152
  {
290
153
  "action_name" => @action_name.to_s,
@@ -296,143 +159,277 @@ module ActionWebhook
296
159
  }
297
160
  end
298
161
 
299
- # Restores state from serialized data
300
162
  def deserialize(data)
301
163
  @action_name = data["action_name"].to_sym
302
164
  @webhook_details = data["webhook_details"]
303
165
  @params = data["params"]
304
166
  @attempts = data["attempts"] || 0
305
167
 
306
- # Restore instance variables
307
- data["instance_variables"].each do |name, value|
308
- instance_variable_set("@#{name}", value)
168
+ restore_instance_variables(data["instance_variables"])
169
+ end
170
+
171
+ class << self
172
+ def method_missing(method_name, *args, &block)
173
+ if public_instance_methods(false).include?(method_name)
174
+ webhook = new
175
+ webhook.send(method_name, *args, &block)
176
+ else
177
+ super
178
+ end
179
+ end
180
+
181
+ def respond_to_missing?(method_name, include_private = false)
182
+ public_instance_methods(false).include?(method_name) || super
183
+ end
184
+
185
+ def deliveries
186
+ @deliveries ||= []
187
+ end
188
+
189
+ def clear_deliveries
190
+ @deliveries = []
191
+ end
192
+
193
+ def after_deliver(method_name = nil, &block)
194
+ self.after_deliver_callback = block_given? ? block : method_name
195
+ end
196
+
197
+ def after_retries_exhausted(method_name = nil, &block)
198
+ self.after_retries_exhausted_callback = block_given? ? block : method_name
309
199
  end
310
200
  end
311
201
 
312
202
  private
313
203
 
314
- # Collects all non-system instance variables for serialization
315
- def collect_instance_variables
316
- result = {}
204
+ EXCLUDED_INSTANCE_VARIABLES = %w[@action_name @webhook_details @params @attempts].freeze
205
+
206
+ def invoke_callback(callback, response)
207
+ return unless callback
208
+
209
+ case callback
210
+ when Symbol
211
+ send(callback, response)
212
+ when Proc
213
+ callback.call(self, response)
214
+ end
215
+ end
216
+
217
+ def extract_instance_variables
218
+ assigns = {}
317
219
  instance_variables.each do |ivar|
318
- # Skip internal instance variables
319
220
  next if ivar.to_s.start_with?("@_")
320
- next if %i[@action_name @webhook_details @params @attempts].include?(ivar)
221
+ next if EXCLUDED_INSTANCE_VARIABLES.include?(ivar.to_s)
321
222
 
322
- # Add to result without @
323
- result[ivar.to_s[1..]] = instance_variable_get(ivar)
223
+ assigns[ivar.to_s[1..].to_sym] = instance_variable_get(ivar)
324
224
  end
325
- result
225
+ assigns
326
226
  end
327
227
 
328
- # Find the engine root path
329
- def engine_root
330
- return nil unless defined?(Rails::Engine)
228
+ def build_headers(detail_headers)
229
+ headers = default_headers.merge(detail_headers)
230
+ headers["Content-Type"] = "application/json" unless headers.key?("Content-Type")
231
+ headers["X-Webhook-Attempt"] = @attempts.to_s if @attempts.positive?
232
+ headers
233
+ end
331
234
 
332
- mod = self.class.module_parent
333
- while mod != Object
334
- constants = mod.constants.select do |c|
335
- const = mod.const_get(c)
336
- const.is_a?(Class) && const < Rails::Engine
337
- end
235
+ def send_webhook_request(url, payload, headers)
236
+ HTTParty.post(url, body: payload.to_json, headers: headers, timeout: 10)
237
+ end
338
238
 
339
- return mod.const_get(constants.first).root.to_s if constants.any?
239
+ def build_response_hash(response, url)
240
+ {
241
+ success: response.success?,
242
+ status: response.code,
243
+ body: response.body,
244
+ url: url,
245
+ attempt: @attempts
246
+ }
247
+ end
340
248
 
341
- mod = mod.module_parent
249
+ def build_error_response_hash(error, url)
250
+ {
251
+ success: false,
252
+ error: error.message,
253
+ url: url,
254
+ attempt: @attempts
255
+ }
256
+ end
257
+
258
+ def log_webhook_result(response, url)
259
+ if response.success?
260
+ logger.info("Webhook delivered successfully: #{url} (Status: #{response.code}, Attempt: #{@attempts})")
261
+ else
262
+ logger.warn("Webhook delivery failed with HTTP error: #{url} (Status: #{response.code}, Attempt: #{@attempts})")
342
263
  end
264
+ end
343
265
 
344
- nil
266
+ def log_webhook_error(error, url)
267
+ logger.error("Webhook delivery failed: #{error.message} for URL: #{url} (Attempt: #{@attempts})")
345
268
  end
346
269
 
347
- # Similarly update enqueue_delivery:
348
- def enqueue_delivery(delivery_method, options = {})
349
- options = options.dup
350
- queue = options.delete(:queue) || self.class.deliver_later_queue_name
270
+ def find_template_path(event_name)
271
+ webhook_class_name = self.class.name.underscore
272
+ possible_paths = build_template_paths(webhook_class_name, event_name)
273
+ possible_paths.find { |path| File.exist?(path) }
274
+ end
351
275
 
352
- args = [delivery_method.to_s]
276
+ def build_template_paths(webhook_class_name, event_name)
277
+ [
278
+ File.join(Rails.root.to_s, "app/webhooks/#{webhook_class_name}/#{event_name}.json.erb"),
279
+ engine_template_path(webhook_class_name, event_name),
280
+ namespaced_engine_template_path(webhook_class_name, event_name)
281
+ ].compact
282
+ end
353
283
 
354
- # Get the actual job class by evaluating the proc
355
- job_class = self.class.delivery_job.is_a?(Proc) ? self.class.delivery_job.call : self.class.delivery_job
284
+ def engine_template_path(webhook_class_name, event_name)
285
+ return unless engine_root
356
286
 
357
- # Serialize the webhook
358
- serialized_webhook = serialize
287
+ File.join(engine_root, "app/webhooks/#{webhook_class_name}/#{event_name}.json.erb")
288
+ end
359
289
 
360
- # Use the delivery job to perform the delivery with serialized data
361
- if queue
362
- job_class.set(queue: queue).perform_later(*args, serialized_webhook)
363
- else
364
- job_class.set(wait: options[:wait]).perform_later(*args, serialized_webhook) if options[:wait]
365
- job_class.perform_later(*args, serialized_webhook) unless options[:wait]
366
- end
290
+ def namespaced_engine_template_path(webhook_class_name, event_name)
291
+ return unless engine_root && self.class.module_parent != Object
292
+
293
+ parent_name = self.class.module_parent.name.underscore
294
+ class_name = webhook_class_name.split("/").last
295
+ File.join(engine_root, "app/webhooks/#{parent_name}/#{class_name}/#{event_name}.json.erb")
367
296
  end
368
297
 
369
- # Logger for webhook errors
370
- def logger
371
- Rails.logger
298
+ def render_json_template(template_path, assigns)
299
+ template = ERB.new(File.read(template_path))
300
+ json = template.result_with_hash(assigns)
301
+ JSON.parse(json)
372
302
  end
373
303
 
374
- # Class methods
375
- class << self
376
- # Handle method calls on the class
377
- def method_missing(method_name, *args, &block)
378
- if public_instance_methods(false).include?(method_name)
379
- # Create a new instance
380
- webhook = new
304
+ def collect_instance_variables
305
+ result = {}
306
+ instance_variables.each do |ivar|
307
+ next if ivar.to_s.start_with?("@_")
308
+ next if EXCLUDED_INSTANCE_VARIABLES.include?(ivar.to_s)
381
309
 
382
- # Call the instance method
383
- webhook.send(method_name, *args, &block)
384
- else
385
- super
386
- end
310
+ result[ivar.to_s[1..]] = instance_variable_get(ivar)
387
311
  end
312
+ result
313
+ end
388
314
 
389
- def respond_to_missing?(method_name, include_private = false)
390
- public_instance_methods(false).include?(method_name) || super
315
+ def restore_instance_variables(variables)
316
+ variables.each do |name, value|
317
+ instance_variable_set("@#{name}", value)
391
318
  end
319
+ end
392
320
 
393
- # Test delivery collection
394
- def deliveries
395
- @deliveries ||= []
321
+ def retry_with_backoff
322
+ delay = calculate_backoff_delay
323
+ logger.info("Scheduling webhook retry #{@attempts + 1}/#{self.class.max_retries} in #{delay} seconds")
324
+
325
+ job_class = resolve_job_class
326
+ serialized_webhook = serialize
327
+
328
+ enqueue_retry_job(job_class, serialized_webhook, delay)
329
+ end
330
+
331
+ def calculate_backoff_delay
332
+ base_delay = self.class.retry_delay
333
+ multiplier = case self.class.retry_backoff
334
+ when :exponential
335
+ 2**(@attempts - 1)
336
+ when :linear
337
+ @attempts
338
+ else
339
+ 1
340
+ end
341
+
342
+ base_delay * multiplier + rand(self.class.retry_jitter)
343
+ end
344
+
345
+ def resolve_job_class
346
+ self.class.delivery_job.is_a?(Proc) ? self.class.delivery_job.call : self.class.delivery_job
347
+ end
348
+
349
+ def enqueue_retry_job(job_class, serialized_webhook, delay)
350
+ if deliver_later_queue_name
351
+ job_class.set(queue: deliver_later_queue_name, wait: delay).perform_later("deliver_now", serialized_webhook)
352
+ else
353
+ job_class.set(wait: delay).perform_later("deliver_now", serialized_webhook)
396
354
  end
355
+ end
397
356
 
398
- # Reset the test delivery collection
399
- def clear_deliveries
400
- @deliveries = []
357
+ def engine_root
358
+ return nil unless defined?(Rails::Engine)
359
+
360
+ find_engine_root_for_module(self.class.module_parent)
361
+ end
362
+
363
+ def find_engine_root_for_module(mod)
364
+ while mod != Object
365
+ engine_constant = find_engine_constant(mod)
366
+ return mod.const_get(engine_constant).root.to_s if engine_constant
367
+
368
+ mod = mod.module_parent
401
369
  end
402
370
 
403
- # Register callback for successful delivery
404
- def after_deliver(method_name = nil, &block)
405
- self.after_deliver_callback = block_given? ? block : method_name
371
+ nil
372
+ end
373
+
374
+ def find_engine_constant(mod)
375
+ mod.constants.find do |c|
376
+ const = mod.const_get(c)
377
+ const.is_a?(Class) && const < Rails::Engine
406
378
  end
379
+ end
407
380
 
408
- # Register callback for when retries are exhausted
409
- def after_retries_exhausted(method_name = nil, &block)
410
- self.after_retries_exhausted_callback = block_given? ? block : method_name
381
+ def enqueue_delivery(delivery_method, options = {})
382
+ options = options.dup
383
+ queue = options.delete(:queue) || self.class.deliver_later_queue_name
384
+ job_class = resolve_job_class
385
+ serialized_webhook = serialize
386
+
387
+ enqueue_job(job_class, delivery_method.to_s, serialized_webhook, queue, options)
388
+ end
389
+
390
+ def enqueue_job(job_class, delivery_method, serialized_webhook, queue, options)
391
+ if queue
392
+ job_class.set(queue: queue).perform_later(delivery_method, serialized_webhook)
393
+ elsif options[:wait]
394
+ job_class.set(wait: options[:wait]).perform_later(delivery_method, serialized_webhook)
395
+ else
396
+ job_class.perform_later(delivery_method, serialized_webhook)
411
397
  end
412
398
  end
399
+
400
+ def logger
401
+ Rails.logger
402
+ end
413
403
  end
414
404
 
415
- # Delivery messenger for ActionMailer-like API
416
405
  class DeliveryMessenger
417
406
  def initialize(webhook)
418
407
  @webhook = webhook
419
408
  end
420
409
 
421
410
  def deliver_now
422
- if @webhook.respond_to?(:perform_deliveries) && !@webhook.perform_deliveries
423
- # Skip delivery
424
- nil
425
- elsif @webhook.class.delivery_method == :test
426
- # Test delivery
427
- ActionWebhook::Base.deliveries << @webhook
428
- else
429
- # Normal delivery
430
- @webhook.deliver_now
431
- end
411
+ return nil if skip_delivery?
412
+ return test_delivery if test_mode?
413
+
414
+ @webhook.deliver_now
432
415
  end
433
416
 
434
417
  def deliver_later(options = {})
435
418
  @webhook.deliver_later(options)
436
419
  end
420
+
421
+ private
422
+
423
+ def skip_delivery?
424
+ @webhook.respond_to?(:perform_deliveries) && !@webhook.perform_deliveries
425
+ end
426
+
427
+ def test_mode?
428
+ @webhook.class.delivery_method == :test
429
+ end
430
+
431
+ def test_delivery
432
+ ActionWebhook::Base.deliveries << @webhook
433
+ end
437
434
  end
438
435
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionWebhook
4
- VERSION = "0.1.1"
4
+ VERSION = "1.0.0"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_webhook
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vinay Uttam Vemparala
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-27 00:00:00.000000000 Z
10
+ date: 2025-06-06 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: httparty
@@ -24,33 +24,53 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: 0.18.1
26
26
  - !ruby/object:Gem::Dependency
27
- name: rails
27
+ name: activejob
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '6.0'
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '8.0'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '6.0'
43
+ - - "<"
44
+ - !ruby/object:Gem::Version
45
+ version: '8.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: globalid
28
48
  requirement: !ruby/object:Gem::Requirement
29
49
  requirements:
30
50
  - - "~>"
31
51
  - !ruby/object:Gem::Version
32
- version: '7'
52
+ version: '1.0'
33
53
  type: :runtime
34
54
  prerelease: false
35
55
  version_requirements: !ruby/object:Gem::Requirement
36
56
  requirements:
37
57
  - - "~>"
38
58
  - !ruby/object:Gem::Version
39
- version: '7'
59
+ version: '1.0'
40
60
  - !ruby/object:Gem::Dependency
41
61
  name: yard
42
62
  requirement: !ruby/object:Gem::Requirement
43
63
  requirements:
44
- - - ">="
64
+ - - "~>"
45
65
  - !ruby/object:Gem::Version
46
- version: '0'
66
+ version: '0.9'
47
67
  type: :development
48
68
  prerelease: false
49
69
  version_requirements: !ruby/object:Gem::Requirement
50
70
  requirements:
51
- - - ">="
71
+ - - "~>"
52
72
  - !ruby/object:Gem::Version
53
- version: '0'
73
+ version: '0.9'
54
74
  description: A Rails library for triggering webhooks. Inspired by ActionMailer from
55
75
  Rails
56
76
  email:
@@ -63,13 +83,13 @@ files:
63
83
  - lib/action_webhook/base.rb
64
84
  - lib/action_webhook/delivery_job.rb
65
85
  - lib/action_webhook/version.rb
66
- homepage: https://github.com/vinayuttam/action_webhook.git
86
+ homepage: https://github.com/vinayuttam/action_webhook
67
87
  licenses:
68
88
  - MIT
69
89
  metadata:
70
- homepage_uri: https://github.com/vinayuttam/action_webhook.git
71
- source_code_uri: https://github.com/vinayuttam/action_webhook.git
72
- changelog_uri: https://github.com/vinayuttam/action_webhook.git/blob/main/CHANGELOG.md
90
+ homepage_uri: https://github.com/vinayuttam/action_webhook
91
+ source_code_uri: https://github.com/vinayuttam/action_webhook/tree/main
92
+ changelog_uri: https://github.com/vinayuttam/action_webhook/blob/main/CHANGELOG.md
73
93
  rdoc_options: []
74
94
  require_paths:
75
95
  - lib