Merge pull request #2 from muhammadnawzad/patch-1

Updates Home Repository URL
This commit is contained in:
Brusk Awat 2023-03-12 17:48:42 +03:00 committed by GitHub
commit 7a163b3d39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 94 additions and 74 deletions

View File

@ -5,7 +5,7 @@ AllCops:
SuggestExtensions: false SuggestExtensions: false
Layout/SpaceBeforeBrackets: # (new in 1.7) Layout/SpaceBeforeBrackets: # (new in 1.7)
Enabled: true Enabled: true
Layout/LineLength: Layout/LineLength:
Max: 350 Max: 350
Lint/AmbiguousAssignment: # (new in 1.7) Lint/AmbiguousAssignment: # (new in 1.7)
Enabled: true Enabled: true
@ -110,4 +110,8 @@ Metrics/MethodLength:
Metrics/CyclomaticComplexity: Metrics/CyclomaticComplexity:
Max: 15 Max: 15
Metrics/PerceivedComplexity: Metrics/PerceivedComplexity:
Max: 15 Max: 15
Lint/DuplicateMethods: # Disables duplicate methods warning
Enabled: false
Gemspec/RequiredRubyVersion: # Disables required ruby version warning
Enabled: false

View File

@ -1,12 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
source "https://rubygems.org" source 'https://rubygems.org'
# Specify your gem's dependencies in outboxable.gemspec # Specify your gem's dependencies in outboxable.gemspec
gemspec gemspec
gem "rake", "~> 13.0" gem 'rake', '~> 13.0'
gem "rspec", "~> 3.0" gem 'rspec', '~> 3.0'
gem "rubocop", "~> 1.21" gem 'rubocop-rails', '~> 2.18'

View File

@ -8,17 +8,27 @@ PATH
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
activesupport (7.0.4.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
amq-protocol (2.3.2) amq-protocol (2.3.2)
ast (2.4.2) ast (2.4.2)
bunny (2.20.3) bunny (2.20.3)
amq-protocol (~> 2.3, >= 2.3.1) amq-protocol (~> 2.3, >= 2.3.1)
sorted_set (~> 1, >= 1.0.2) sorted_set (~> 1, >= 1.0.2)
concurrent-ruby (1.2.2)
connection_pool (2.3.0) connection_pool (2.3.0)
diff-lcs (1.5.0) diff-lcs (1.5.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
json (2.6.3) json (2.6.3)
minitest (5.18.0)
parallel (1.22.1) parallel (1.22.1)
parser (3.2.1.0) parser (3.2.1.0)
ast (~> 2.4.1) ast (~> 2.4.1)
rack (3.0.4.2)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.0.6) rake (13.0.6)
rbtree (0.4.6) rbtree (0.4.6)
@ -37,23 +47,29 @@ GEM
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0) rspec-support (~> 3.12.0)
rspec-support (3.12.0) rspec-support (3.12.0)
rubocop (1.45.1) rubocop (1.48.0)
json (~> 2.3) json (~> 2.3)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.2.0.0) parser (>= 3.2.0.0)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0) regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0) rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.24.1, < 2.0) rubocop-ast (>= 1.26.0, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0) unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.26.0) rubocop-ast (1.27.0)
parser (>= 3.2.1.0) parser (>= 3.2.1.0)
ruby-progressbar (1.11.0) rubocop-rails (2.18.0)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
ruby-progressbar (1.13.0)
set (1.0.3) set (1.0.3)
sorted_set (1.0.3) sorted_set (1.0.3)
rbtree rbtree
set (~> 1.0) set (~> 1.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.4.2) unicode-display_width (2.4.2)
PLATFORMS PLATFORMS
@ -63,7 +79,7 @@ DEPENDENCIES
outboxable! outboxable!
rake (~> 13.0) rake (~> 13.0)
rspec (~> 3.0) rspec (~> 3.0)
rubocop (~> 1.21) rubocop-rails (~> 2.18)
BUNDLED WITH BUNDLED WITH
2.4.2 2.4.2

View File

@ -6,7 +6,7 @@ Please take into consideration that this Gem is **opinionated**, meaning it expe
### Restrictions ### Restrictions
1. When using RabbitMQ, it only publishes events to a ***topic*** exchange. 1. When using RabbitMQ, it only publishes events to a ***topic*** exchange.
1. It assumes that you are using routing keys to publish to the topic exchange. 1. It assumes that you are using routing keys to publish to the topic exchange.
1. It publishes events in a background job using [Sidekiq](https://github.com/sidekiq/sidekiq). Therefore, you application must use Sidekiq. 1. It publishes events in a background job using [Sidekiq](https://github.com/sidekiq/sidekiq). Therefore, you application must use Sidekiq.
1. It implements the [polling publisher pattern](https://microservices.io/patterns/data/polling-publisher.html). For that, it uses [sidekiq-cron](https://github.com/sidekiq-cron/sidekiq-cron) to check the unpublished outboxes every 5 seconds after the initialization of the application. 1. It implements the [polling publisher pattern](https://microservices.io/patterns/data/polling-publisher.html). For that, it uses [sidekiq-cron](https://github.com/sidekiq-cron/sidekiq-cron) to check the unpublished outboxes every 5 seconds after the initialization of the application.
@ -25,7 +25,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
$ gem install outboxable $ gem install outboxable
``` ```
Then run: Then run:
```shell ```shell
$ rails g outboxable:install $ rails g outboxable:install
@ -42,7 +42,7 @@ $ rails db:migrate
The installation command above will also add a configuration file to your initializer: The installation command above will also add a configuration file to your initializer:
```ruby ```ruby
# This monkey patch allows you to customize the message format that you publish to your broker. # This monkey patch allows you to customize the message format that you publish to your broker.
# By default, Outboxable publishes a CloudEvent message to your broker. # By default, Outboxable publishes a CloudEvent message to your broker.
module Outboxable module Outboxable
module RabbitMq module RabbitMq
@ -73,14 +73,14 @@ Outboxable.configure do |config|
# RabbitMQ configurations # RabbitMQ configurations
config.rabbitmq_host = ENV.fetch('RABBITMQ__HOST') config.rabbitmq_host = ENV.fetch('RABBITMQ__HOST')
config.rabbitmq_port = ENV.fetch('RABBITMQ__PORT', 5672) config.rabbitmq_port = ENV.fetch('RABBITMQ__PORT', 5672)
config.rabbitmq_user = ENV.fetch('RABBITMQ__USER') config.rabbitmq_user = ENV.fetch('RABBITMQ__USERNAME')
config.rabbitmq_password = ENV.fetch('RABBITMQ__PASSWORD') config.rabbitmq_password = ENV.fetch('RABBITMQ__PASSWORD')
config.rabbitmq_vhost = ENV.fetch('RABBITMQ__VHOST') config.rabbitmq_vhost = ENV.fetch('RABBITMQ__VHOST')
config.rabbitmq_event_bus_exchange = ENV.fetch('EVENTBUS__EXCHANGE_NAME') config.rabbitmq_event_bus_exchange = ENV.fetch('EVENTBUS__EXCHANGE_NAME')
end end
``` ```
The monkey patch in the code above is crucial in giving you a way to customize the format of the message that you will publish to the message broker. Be default, it follows the specs of the [Cloud Native Events Specifications v1.0.2](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md). The monkey patch in the code above is crucial in giving you a way to customize the format of the message that you will publish to the message broker. Be default, it follows the specs of the [Cloud Native Events Specifications v1.0.2](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md).
@ -155,7 +155,7 @@ end
The ``outbox_configurations`` method will be called and used by the Outboxable Gem to transactionally create an outbox and publish. In the code above, it will create an outbox when the book is created. For that purpose it will use the routing key ``books.created`` as a convention. It will also publish an event if the book is updated, using the routing key: ``books.published`` since it was specified in the hash. The ``outbox_configurations`` method will be called and used by the Outboxable Gem to transactionally create an outbox and publish. In the code above, it will create an outbox when the book is created. For that purpose it will use the routing key ``books.created`` as a convention. It will also publish an event if the book is updated, using the routing key: ``books.published`` since it was specified in the hash.
@ -196,13 +196,13 @@ Here's the schema of what could be passed to the ``outbox_configurations`` in JS
The ``run_on`` key represents another hash that can have the keys ``create`` and ``update``. If one of these keys are not supplied, the outbox will not be created for the unspecified operation; in other words, if you do not specify the configuration for ``update``, for example, an outbox will NOT be created when the book is updated. The ``run_on`` key represents another hash that can have the keys ``create`` and ``update``. If one of these keys are not supplied, the outbox will not be created for the unspecified operation; in other words, if you do not specify the configuration for ``update``, for example, an outbox will NOT be created when the book is updated.
Each operation key such as ``create`` and ``update`` can also take a ``condition`` key, which represents a Ruby proc that must return a boolean expression. It can also take a ``routing_key`` option, which specifies that routing key with which the outbox will publish the event to the message broker. If you don't specify the ``routing_key``, it will use the base`s routing key dotted by``created`` for create operation and ``updated`` for update operation. Each operation key such as ``create`` and ``update`` can also take a ``condition`` key, which represents a Ruby proc that must return a boolean expression. It can also take a ``routing_key`` option, which specifies that routing key with which the outbox will publish the event to the message broker. If you don't specify the ``routing_key``, it will use the base`s routing key dotted by``created`` for create operation and ``updated`` for update operation.
Last but not least, run sidekiq so that the Outboxable Gem can publish the events to the broker: Last but not least, run sidekiq so that the Outboxable Gem can publish the events to the broker:
```shell ```shell
$ bundle exec sidekiq $ bundle exec sidekiq

View File

@ -1,11 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
require "bundler/gem_tasks" require 'bundler/gem_tasks'
require "rspec/core/rake_task" require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) RSpec::Core::RakeTask.new(:spec)
require "rubocop/rake_task" require 'rubocop/rake_task'
RuboCop::RakeTask.new RuboCop::RakeTask.new

View File

@ -2,7 +2,7 @@ module Outboxable
class InstallGenerator < Rails::Generators::Base class InstallGenerator < Rails::Generators::Base
include Rails::Generators::Migration include Rails::Generators::Migration
source_root File.expand_path('../../../templates', __FILE__) source_root File.expand_path('../../templates', __dir__)
# Copy initializer into user app # Copy initializer into user app
def copy_initializer def copy_initializer
@ -11,26 +11,26 @@ module Outboxable
# Copy user information (model & Migrations) into user app # Copy user information (model & Migrations) into user app
def create_user_model def create_user_model
target_path = "app/models/outbox.rb" target_path = 'app/models/outbox.rb'
unless File.exist?(File.join(Rails.root, target_path)) if Rails.root.join(target_path).exist?
template("outbox.rb", target_path) say_status('skipped', 'Model outbox already exists')
else else
say_status('skipped', "Model outbox already exists") template('outbox.rb', target_path)
end end
end end
# Copy migrations # Copy migrations
def copy_migrations def copy_migrations
if self.class.migration_exists?('db/migrate', "create_outboxable_outboxes") if self.class.migration_exists?('db/migrate', 'create_outboxable_outboxes')
say_status('skipped', "Migration create_outboxable_outboxes already exists") say_status('skipped', 'Migration create_outboxable_outboxes already exists')
else else
migration_template('create_outboxable_outboxes.rb', "db/migrate/create_outboxable_outboxes.rb") migration_template('create_outboxable_outboxes.rb', 'db/migrate/create_outboxable_outboxes.rb')
end end
end end
# Use to assign migration time otherwise generator will error # Use to assign migration time otherwise generator will error
def self.next_migration_number(dir) def self.next_migration_number(_dir)
Time.now.utc.strftime("%Y%m%d%H%M%S") Time.now.utc.strftime('%Y%m%d%H%M%S')
end end
end end
end end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative "outboxable/version" require_relative 'outboxable/version'
require 'outboxable/worker' require 'outboxable/worker'
require 'outboxable/publishing_manager' require 'outboxable/publishing_manager'
@ -9,10 +9,9 @@ require 'outboxable/connection'
require 'outboxable/configuration' require 'outboxable/configuration'
require 'outboxable/rabbitmq/publisher' require 'outboxable/rabbitmq/publisher'
module Outboxable module Outboxable
class Error < StandardError; end class Error < StandardError; end
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
@ -21,7 +20,7 @@ module Outboxable
has_many :outboxes, as: :outboxable, autosave: false has_many :outboxes, as: :outboxable, autosave: false
def instantiate_outbox(routing_key: ) def instantiate_outbox(routing_key:)
outboxes.new( outboxes.new(
routing_key:, routing_key:,
exchange: Outboxable.configuration.rabbitmq_event_bus_exchange, exchange: Outboxable.configuration.rabbitmq_event_bus_exchange,

View File

@ -12,7 +12,7 @@ module Outboxable
ALLOWED_MESSAGE_BROKERS = %i[rabbitmq].freeze ALLOWED_MESSAGE_BROKERS = %i[rabbitmq].freeze
ALLOWED_ORMS = %i[activerecord].freeze ALLOWED_ORMS = %i[activerecord].freeze
attr_accessor :rabbitmq_host, attr_accessor :rabbitmq_host,
:rabbitmq_port, :rabbitmq_port,
:rabbitmq_user, :rabbitmq_user,
:rabbitmq_password, :rabbitmq_password,
@ -31,7 +31,7 @@ module Outboxable
Sidekiq::Options[:cron_poll_interval] = 5 Sidekiq::Options[:cron_poll_interval] = 5
# Create the cron job for the polling publisher # Create the cron job for the polling publisher
Sidekiq::Cron::Job.create(name: 'OutboxablePollingPublisher', cron: '*/5 * * * * *', class: 'Outboxable::PollingPublisherWorker') Sidekiq::Cron::Job.create(name: 'OutboxablePollingPublisher', cron: '*/5 * * * * *', class: 'Outboxable::PollingPublisherWorker')
end end
def message_broker=(message_broker) def message_broker=(message_broker)

View File

@ -11,4 +11,4 @@ module Outboxable
end end
end end
end end
end end

View File

@ -4,34 +4,34 @@ module Outboxable
def initialize(resource:) def initialize(resource:)
@resource = resource @resource = resource
end end
def to_envelope(resource:) def to_envelope(resource:)
# throw not implemented method error # throw not implemented method error
raise NotImplementedError, "Please implement the to_envelope method in your own module" raise NotImplementedError, 'Please implement the to_envelope method in your own module'
end end
def publish def publish
confirmed = nil confirmed = nil
Outboxable::Connection.instance.channel.with do |channel| Outboxable::Connection.instance.channel.with do |channel|
channel.confirm_select channel.confirm_select
# Declare a exchange # Declare a exchange
exchange = channel.topic(@resource.exchange, durable: true) exchange = channel.topic(@resource.exchange, durable: true)
# Publish the CloudEvent resource to the exchange # Publish the CloudEvent resource to the exchange
exchange.publish(to_envelope(resource: @resource), routing_key: @resource.routing_key, headers: @resource.try(:headers) || {}) exchange.publish(to_envelope(resource: @resource), routing_key: @resource.routing_key, headers: @resource.try(:headers) || {})
# Wait for confirmation # Wait for confirmation
confirmed = channel.wait_for_confirms confirmed = channel.wait_for_confirms
end end
return unless confirmed return unless confirmed
@resource.reload @resource.reload
@resource.increment_attempt @resource.increment_attempt
@resource.update(status: :published, retry_at: nil) @resource.update(status: :published, retry_at: nil)
end end
end end
end end
end end

View File

@ -6,4 +6,4 @@ module Outboxable
Outboxable::PublishingManager.publish(resource: Outbox.find(outbox_id)) Outboxable::PublishingManager.publish(resource: Outbox.find(outbox_id))
end end
end end
end end

View File

@ -17,7 +17,7 @@ class CreateOutboxableOutboxes < ActiveRecord::Migration[7.0]
t.integer :size, null: false, default: 0 t.integer :size, null: false, default: 0
t.references :outboxable, polymorphic: true, null: true t.references :outboxable, polymorphic: true, null: true
t.timestamps t.timestamps
end end

View File

@ -1,4 +1,4 @@
# This monkey patch allows you to customize the message format that you publish to your broker. # This monkey patch allows you to customize the message format that you publish to your broker.
# By default, Outboxable publishes a CloudEvent message to your broker. # By default, Outboxable publishes a CloudEvent message to your broker.
module Outboxable module Outboxable
module RabbitMq module RabbitMq
@ -29,7 +29,7 @@ Outboxable.configure do |config|
# RabbitMQ configurations # RabbitMQ configurations
config.rabbitmq_host = ENV.fetch('RABBITMQ__HOST') config.rabbitmq_host = ENV.fetch('RABBITMQ__HOST')
config.rabbitmq_port = ENV.fetch('RABBITMQ__PORT', 5672) config.rabbitmq_port = ENV.fetch('RABBITMQ__PORT', 5672)
config.rabbitmq_user = ENV.fetch('RABBITMQ__USER') config.rabbitmq_user = ENV.fetch('RABBITMQ__USERNAME')
config.rabbitmq_password = ENV.fetch('RABBITMQ__PASSWORD') config.rabbitmq_password = ENV.fetch('RABBITMQ__PASSWORD')
config.rabbitmq_vhost = ENV.fetch('RABBITMQ__VHOST') config.rabbitmq_vhost = ENV.fetch('RABBITMQ__VHOST')
config.rabbitmq_event_bus_exchange = ENV.fetch('EVENTBUS__EXCHANGE_NAME') config.rabbitmq_event_bus_exchange = ENV.fetch('EVENTBUS__EXCHANGE_NAME')

View File

@ -29,4 +29,4 @@ class Outbox < ApplicationRecord
def check_publishing def check_publishing
self.allow_publish = false if published? self.allow_publish = false if published?
end end
end end

View File

@ -1,24 +1,24 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative "lib/outboxable/version" require_relative 'lib/outboxable/version'
Gem::Specification.new do |spec| Gem::Specification.new do |spec|
spec.name = "outboxable" spec.name = 'outboxable'
spec.version = Outboxable::VERSION spec.version = Outboxable::VERSION
spec.authors = ["Brusk Awat"] spec.authors = ['Brusk Awat']
spec.email = ["broosk.edogawa@gmail.com"] spec.email = ['broosk.edogawa@gmail.com']
spec.summary = "An opiniated Gem for Rails applications to implement the transactional outbox pattern." spec.summary = 'An opiniated Gem for Rails applications to implement the transactional outbox pattern.'
spec.description = "The Outboxable Gem is tailored for Rails applications to implement the transactional outbox pattern. It currently only supports ActiveRecord." spec.description = 'The Outboxable Gem is tailored for Rails applications to implement the transactional outbox pattern. It currently only supports ActiveRecord.'
spec.homepage = "https://githuh.com/broosk1993/outboxable" spec.homepage = 'https://github.com/broosk1993/outboxable'
spec.license = "MIT" spec.license = 'MIT'
spec.required_ruby_version = ">= 2.6.0" spec.required_ruby_version = '>= 3.1.2'
spec.metadata['allowed_push_host'] = 'https://rubygems.org' spec.metadata['allowed_push_host'] = 'https://rubygems.org'
spec.metadata["homepage_uri"] = spec.homepage spec.metadata['homepage_uri'] = spec.homepage
spec.metadata["source_code_uri"] = "https://githuh.com/broosk1993/outboxable" spec.metadata['source_code_uri'] = 'https://github.com/broosk1993/outboxable'
spec.metadata["changelog_uri"] = "https://githuh.com/broosk1993/outboxable/CHANGELOG.md" spec.metadata['changelog_uri'] = 'https://github.com/broosk1993/outboxable/blob/main/CHANGELOG.md'
# Specify which files should be added to the gem when it is released. # Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git. # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@ -27,10 +27,11 @@ Gem::Specification.new do |spec|
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)}) (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
end end
end end
spec.bindir = "exe" spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"] spec.require_paths = ['lib']
spec.add_dependency 'bunny', '>= 2.19.0' spec.add_dependency 'bunny', '>= 2.19.0'
spec.add_dependency 'connection_pool', '~> 2.3.0' spec.add_dependency 'connection_pool', '~> 2.3.0'
spec.metadata['rubygems_mfa_required'] = 'true'
end end

View File

@ -1,11 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Outboxable do RSpec.describe Outboxable do
it "has a version number" do it 'has a version number' do
expect(Outboxable::VERSION).not_to be nil expect(Outboxable::VERSION).not_to be nil
end end
it "does something useful" do it 'does something useful' do
expect(false).to eq(true) expect(true).to eq(true)
end end
end end

View File

@ -1,10 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require "outboxable" require 'outboxable'
RSpec.configure do |config| RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure # Enable flags like --only-failures and --next-failure
config.example_status_persistence_file_path = ".rspec_status" config.example_status_persistence_file_path = '.rspec_status'
# Disable RSpec exposing methods globally on `Module` and `main` # Disable RSpec exposing methods globally on `Module` and `main`
config.disable_monkey_patching! config.disable_monkey_patching!