outboxable 0.1.8 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab43c50794ab0059ec940956619219fc78cfc1fdc2f2e21e78bd7ac5f24f43dd
4
- data.tar.gz: 7d1f9ac4e934f92b5c257cd1489ee2f9f316998a3b60b6ba8a9d3af18b9baf41
3
+ metadata.gz: 7ff24c145223dcc558818028a2866583266372e1a52cbb0dc63661fdad5f94ea
4
+ data.tar.gz: 64e19fa685b3f77a85c10bc60f9754a32bbe8534b9aaa5cb153253aefb5bbcb1
5
5
  SHA512:
6
- metadata.gz: 5811e8e889c56840cff9d4911853f8929cc11b8959e204965bbfff384c7bc914a32daee6d175563921f0332419ab6201528839b548921e24d70d8428b51c23aa
7
- data.tar.gz: 6f1b34031671e1cf047642d86a37a44e1bf26d9ec0d5a8929923a188d95177f24df264e9dfb0e8c8bf30522308325441d4a192f198daea1a063e606dd4804126
6
+ metadata.gz: 9dca7a575abbc63e9b033e3726229ce8abe6254ea049f220f66c9b675c4395efbfdac1a12da0dc5c31f50a65bfdab939ee23809c24d9e2182864fdd0b703754f
7
+ data.tar.gz: 6673eabf79e2daa9b36bd93c84589dc68f796a22c8b82cf93d035cd9eaaa1b3ba523ee16c9c121c719befe9b1527b3bf32a6c2467ee023b9bfd527b209575ef3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- outboxable (0.1.8)
4
+ outboxable (1.0.2)
5
5
  bunny (>= 2.19.0)
6
6
  connection_pool (~> 2.3.0)
7
7
 
@@ -3,24 +3,37 @@ module Outboxable
3
3
  include Rails::Generators::Migration
4
4
 
5
5
  source_root File.expand_path('../../templates', __dir__)
6
+ class_option :orm, type: :string, default: 'activerecord'
7
+
8
+ def initialize(*)
9
+ super(*)
10
+
11
+ @orm = options[:orm] || 'activerecord'
12
+ %w[activerecord mongoid].include?(@orm) || raise(ArgumentError, 'Invalid ORM. Only ActiveRecord and Mongoid are supported.')
13
+ end
6
14
 
7
15
  # Copy initializer into user app
8
16
  def copy_initializer
9
- copy_file('initializer.rb', 'config/initializers/z_outboxable.rb')
17
+ copy_file('activerecod_initializer.rb', 'config/initializers/z_outboxable.rb') if @orm == 'activerecord'
18
+ copy_file('mongoid_initializer.rb', 'config/initializers/z_outboxable.rb') if @orm == 'mongoid'
10
19
  end
11
20
 
12
21
  # Copy user information (model & Migrations) into user app
13
22
  def create_user_model
14
23
  target_path = 'app/models/outbox.rb'
24
+
15
25
  if Rails.root.join(target_path).exist?
16
26
  say_status('skipped', 'Model outbox already exists')
17
27
  else
18
- template('outbox.rb', target_path)
28
+ template('activerecrod_outbox.rb', target_path) if @orm == 'activerecord'
29
+ template('mongoid_outbox.rb', target_path) if @orm == 'mongoid'
19
30
  end
20
31
  end
21
32
 
22
33
  # Copy migrations
23
34
  def copy_migrations
35
+ return if @orm == 'mongoid'
36
+
24
37
  if self.class.migration_exists?('db/migrate', 'create_outboxable_outboxes')
25
38
  say_status('skipped', 'Migration create_outboxable_outboxes already exists')
26
39
  else
@@ -6,11 +6,17 @@ module Outboxable
6
6
  def self.configure
7
7
  self.configuration ||= Configuration.new
8
8
  yield(configuration)
9
+
10
+ # In accordance to sidekiq-cron README: https://github.com/sidekiq-cron/sidekiq-cron#under-the-hood
11
+ Sidekiq::Options[:cron_poll_interval] = 5
12
+
13
+ # Create the cron job for the polling publisher
14
+ Sidekiq::Cron::Job.create(name: 'OutboxablePollingPublisher', cron: '*/5 * * * * *', class: 'Outboxable::PollingPublisherWorker', args: [{ orm: configuration.orm }])
9
15
  end
10
16
 
11
17
  class Configuration
12
18
  ALLOWED_MESSAGE_BROKERS = %i[rabbitmq].freeze
13
- ALLOWED_ORMS = %i[activerecord].freeze
19
+ ALLOWED_ORMS = %i[activerecord mongoid].freeze
14
20
 
15
21
  attr_accessor :rabbitmq_host,
16
22
  :rabbitmq_port,
@@ -26,12 +32,6 @@ module Outboxable
26
32
  raise Error, 'Outboxable Gem only supports Rails but you application does not seem to be a Rails app' unless Object.const_defined?('Rails')
27
33
  raise Error, 'Outboxable Gem only support Rails version 7 and newer' if Rails::VERSION::MAJOR < 7
28
34
  raise Error, 'Outboxable Gem uses the sidekiq-cron Gem. Make sure you add it to your project' unless Object.const_defined?('Sidekiq::Cron')
29
-
30
- # In accordance to sidekiq-cron README: https://github.com/sidekiq-cron/sidekiq-cron#under-the-hood
31
- Sidekiq::Options[:cron_poll_interval] = 5
32
-
33
- # Create the cron job for the polling publisher
34
- Sidekiq::Cron::Job.create(name: 'OutboxablePollingPublisher', cron: '*/5 * * * * *', class: 'Outboxable::PollingPublisherWorker')
35
35
  end
36
36
 
37
37
  def message_broker=(message_broker)
@@ -3,14 +3,27 @@ module Outboxable
3
3
  include Sidekiq::Job
4
4
  sidekiq_options queue: 'critical'
5
5
 
6
- def perform
6
+ def perform(args)
7
+ orm = args['orm']
8
+ orm == 'mongoid' ? perform_mongoid(orm) : perform_activerecord(orm)
9
+ end
10
+
11
+ def perform_activerecord(orm)
7
12
  Outbox.pending.where(last_attempted_at: [..Time.zone.now, nil]).find_in_batches(batch_size: 100).each do |batch|
8
13
  batch.each do |outbox|
9
14
  # This is to prevent a job from being retried too many times. Worst-case scenario is 1 minute delay in jobs.
10
- Outboxable::Worker.perform_async(outbox.id)
15
+ Outboxable::Worker.perform_async(outbox.id, orm)
11
16
  outbox.update(last_attempted_at: 1.minute.from_now, status: :processing, allow_publish: false)
12
17
  end
13
18
  end
14
19
  end
20
+
21
+ def perform_mongoid(orm)
22
+ Outbox.pending.where(last_attempted_at: [..Time.zone.now, nil]).each do |outbox|
23
+ # This is to prevent a job from being retried too many times. Worst-case scenario is 1 minute delay in jobs.
24
+ Outboxable::Worker.perform_async(outbox.idempotency_key, orm)
25
+ outbox.update(last_attempted_at: 1.minute.from_now, status: :processing, allow_publish: false)
26
+ end
27
+ end
15
28
  end
16
29
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Outboxable
4
- VERSION = '0.1.8'
4
+ VERSION = '1.0.2'
5
5
  end
@@ -4,8 +4,9 @@ module Outboxable
4
4
  class Worker
5
5
  include ::Sidekiq::Job
6
6
 
7
- def perform(outbox_id)
8
- Outboxable::PublishingManager.publish(resource: Outbox.find(outbox_id))
7
+ def perform(outbox_id, orm)
8
+ Outboxable::PublishingManager.publish(resource: Outbox.find(outbox_id)) if orm == 'activerecord'
9
+ Outboxable::PublishingManager.publish(resource: Outbox.find_by!(idempotency_key: outbox_id)) if orm == 'mongoid'
9
10
  end
10
11
  end
11
12
  end
data/lib/outboxable.rb CHANGED
@@ -20,7 +20,7 @@ module Outboxable
20
20
  after_create :instantiate_outbox_for_create, if: proc { |object| object.check_outbox_condition(object:, operation: :create) }
21
21
  after_update :instantiate_outbox_for_update, if: proc { |object| object.check_outbox_condition(object:, operation: :update) }
22
22
 
23
- has_many :outboxes, as: :outboxable, autosave: false
23
+ has_many :outboxes, as: :outboxable, dependent: :destroy
24
24
 
25
25
  def instantiate_outbox(routing_key:)
26
26
  outboxes.new(
@@ -4,7 +4,7 @@ class Outbox < ApplicationRecord
4
4
  before_save :check_publishing
5
5
  # Callbacks
6
6
  before_create :set_last_attempted_at
7
- after_commit :publish, if: :allow_publish?
7
+ after_save :publish, if: :allow_publish
8
8
  # Enums
9
9
  enum status: { pending: 0, processing: 1, published: 2, failed: 3 }
10
10
  enum size: { single: 0, batch: 1 }
@@ -0,0 +1,36 @@
1
+ # This monkey patch allows you to customize the message format that you publish to your broker.
2
+ # By default, Outboxable publishes a CloudEvent message to your broker.
3
+ module Outboxable
4
+ module RabbitMq
5
+ class Publisher
6
+ # Override this method to customize the message format that you publish to your broker
7
+ # DO NOT CHANGE THE METHOD SIGNATURE
8
+ def to_envelope(resource:)
9
+ {
10
+ id: resource.id,
11
+ source: 'http://localhost:3000',
12
+ specversion: '1.0',
13
+ type: resource.routing_key,
14
+ datacontenttype: 'application/json',
15
+ data: resource.payload
16
+ }.to_json
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ Outboxable.configure do |config|
23
+ # Specify the ORM you are using. For now, only ActiveRecord is supported.
24
+ config.orm = :mongoid
25
+
26
+ # Specify the message broker you are using. For now, only RabbitMQ is supported.
27
+ config.message_broker = :rabbitmq
28
+
29
+ # RabbitMQ configurations
30
+ config.rabbitmq_host = ENV.fetch('RABBITMQ__HOST')
31
+ config.rabbitmq_port = ENV.fetch('RABBITMQ__PORT', 5672)
32
+ config.rabbitmq_user = ENV.fetch('RABBITMQ__USERNAME')
33
+ config.rabbitmq_password = ENV.fetch('RABBITMQ__PASSWORD')
34
+ config.rabbitmq_vhost = ENV.fetch('RABBITMQ__VHOST')
35
+ config.rabbitmq_event_bus_exchange = ENV.fetch('EVENTBUS__EXCHANGE_NAME')
36
+ end
@@ -0,0 +1,71 @@
1
+ class Outbox
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ attr_writer :allow_publish
6
+
7
+ # Fields
8
+ field :status, type: String, default: 'pending'
9
+ field :size, type: String, default: 'single'
10
+
11
+ field :exchange, type: String, default: ''
12
+ field :routing_key, type: String, default: ''
13
+
14
+ field :attempts, type: Integer, default: 0
15
+
16
+ field :last_attempted_at, type: DateTime, default: nil
17
+
18
+ field :retry_at, type: DateTime, default: nil
19
+
20
+ field :idempotency_key, type: String
21
+
22
+ field :payload, type: Hash, default: {}
23
+ field :headers, type: Hash, default: {}
24
+
25
+ index({ idempotency_key: 1 }, { unique: true, name: 'idempotency_key_unique_index' })
26
+
27
+ before_save :check_publishing
28
+ before_create :set_idempotency_key
29
+
30
+ # Callbacks
31
+ before_create :set_last_attempted_at
32
+ after_save :publish, if: :allow_publish
33
+
34
+ # Validations
35
+ validates :payload, :exchange, :routing_key, presence: true
36
+
37
+ # Associations
38
+ belongs_to :outboxable, polymorphic: true, optional: true
39
+
40
+ def set_last_attempted_at
41
+ self.last_attempted_at = 10.seconds.from_now
42
+ end
43
+
44
+ def publish
45
+ Outboxable::Worker.perform_async(idempotency_key)
46
+ update(status: :processing, last_attempted_at: 1.minute.from_now, allow_publish: false)
47
+ end
48
+
49
+ def set_idempotency_key
50
+ self.idempotency_key = SecureRandom.uuid if idempotency_key.blank?
51
+ end
52
+
53
+ def check_publishing
54
+ self.allow_publish = false if published?
55
+ end
56
+
57
+ def allow_publish
58
+ return true if @allow_publish.nil?
59
+
60
+ @allow_publish
61
+ end
62
+
63
+ %w[pending processing published failed].each do |status|
64
+ define_method "#{status}?" do
65
+ self.status == status
66
+ end
67
+
68
+ # define scope
69
+ scope status, -> { where(status:) }
70
+ end
71
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: outboxable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brusk Awat
@@ -64,9 +64,11 @@ files:
64
64
  - lib/outboxable/rabbitmq/publisher.rb
65
65
  - lib/outboxable/version.rb
66
66
  - lib/outboxable/worker.rb
67
+ - lib/templates/activerecord_initializer.rb
68
+ - lib/templates/activerecrod_outbox.rb
67
69
  - lib/templates/create_outboxable_outboxes.rb
68
- - lib/templates/initializer.rb
69
- - lib/templates/outbox.rb
70
+ - lib/templates/mongoid_initializer.rb
71
+ - lib/templates/mongoid_outbox.rb
70
72
  - sig/outboxable.rbs
71
73
  homepage: https://github.com/broosk1993/outboxable
72
74
  licenses: